Validating PAdES Signatures
Validation runs off the reader: you hand ValidateSignatures a list of handlers,
it finds every signature field in the document, matches each one to the first
handler that claims it, and returns a result per field.
padesHandler, err := sighandler.NewEtsiPAdESLevelB(nil, nil, nil)
if err != nil {
return err
}
res, err := reader.ValidateSignatures([]model.SignatureHandler{padesHandler})
if err != nil {
return err
}
for i, item := range res {
fmt.Printf("--- Signature %d\n%s\n", i+1, item.String())
}Both the key and the certificate are nil, because validation reads the certificate out of the signature itself.
One handler for every PAdES level
The PAdES handler does not branch on its level when validating. It claims any
signature whose filter is Adobe.PPKLite and whose subfilter is
ETSI.CAdES.detached, then verifies the CMS structure the same way regardless of
whether the file was signed at B-B, B-T or B-LT. So a single
NewEtsiPAdESLevelB(nil, nil, nil) handler covers all three.
Do not reach for NewEtsiPAdESLevelLT to do this. That constructor dereferences
its appender argument, so passing nil panics rather than returning an error. Use
the Level B or Level T constructor when the handler is only going to validate.
Document timestamps are a separate case. They carry the subfilter
ETSI.RFC3161, which the PAdES handler does not claim, so validating a
B-LTA file needs both handlers in the list:
tsHandler, err := sighandler.NewDocTimeStamp("", 0)
if err != nil {
return err
}
handlers := []model.SignatureHandler{padesHandler, tsHandler}An empty server URL and a zero hash are fine here; both are only used when
signing. A signature with no matching handler still appears in the results, with
handler not set in its Errors slice.
Reading the result
item.String() prints a readable summary, and the fields behind it are worth
knowing individually.
| Field | Meaning |
|---|---|
IsSigned | A signature dictionary was found for the field. |
IsVerified | The CMS signature verifies against the bytes covered by its byte range. |
GeneralizedTime | The time from the embedded timestamp token. Zero when the signature has none, which is how you tell B-B from B-T. |
IsCrlFound, IsOcspFound | Revocation data was present in the signature’s Adobe revocation-info attribute. |
DiffResults | Set only by the DocMDP handler. See Sign and configure DocMDP restriction. |
Fields | The form field the signature is attached to. |
IsVerified is a statement about bytes, not about trust. It says the digest
matches and the CMS structure checks out against the certificate embedded in the
file. It says nothing about whether that certificate chains to a root you accept,
and nothing about whether it has since been revoked.
IsTrusted is never set by any handler in the SDK, so the summary always reads
Trusted: Untrusted certificate, even for a signature from a well-known CA.
Chain building and trust decisions are left to the caller: pull the certificates
with PdfSignature.GetCerts and evaluate them against your own trust store.
IsCrlFound and IsOcspFound report on the signed attribute inside the CMS, not
on the document security store. A file whose validation data lives only in the
DSS reports false for both, even though the data is there.
Limitations
ValidateSignatures returns nil, nil when the document has no AcroForm or no
form fields. An empty result slice means there is nothing to validate, not that
validation failed, which is why the example treats a zero-length result as its own
error case.
Per-signature failures do not stop the walk. When a handler’s Validate returns
an error, the message is appended to that result’s Errors and the remaining
signatures are still processed. Check Errors as well as IsVerified.
Validation covers the byte range each signature declares. A signature that only covers part of the file still verifies as long as those bytes are intact; nothing in the result reports how much of the document the signature actually spans.
Run the example
The example opens a file, validates with a single Level B handler, and fails if
the first signature is missing or does not verify. All of it is in main.
git clone https://github.com/unidoc/unipdf-examples.git
cd unipdf-examples/signatures
go run pdf_sign_validate_pades_b_b.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.