LTV Enable a Signed File
A signature can only be checked while the certificates in its chain are still
available and their revocation status can still be looked up. Long-Term
Validation solves that by storing the chain, the OCSP responses and the CRLs
inside the document, in the Document Security Store, so a validator years later
needs nothing from the network. model.LTV collects that data and writes it into
a new revision.
| Where the data goes | Call | Used for validating |
|---|---|---|
| Global DSS entries | EnableChain(chain) | Any signature in the document. |
A VRI entry, plus the global entries | Enable, EnableByName, EnableAll | One specific signature. |
The per-signature form is the recommended one, and the reason it needs its own
revision is that the VRI key is upper(hex(sha1(sig.Contents))). The contents
are not known until the revision holding the signature has been written, and
changing the DSS afterwards would invalidate the signature you just computed. If
everything has to happen in one revision, EnableChain is the only option; see
LTV in one revision.
Doing it
appender, err := model.NewPdfAppender(reader)
if err != nil {
return err
}
ltv, err := model.NewLTV(appender)
if err != nil {
return err
}
if err := ltv.EnableAll(certChain); err != nil {
return err
}
return appender.WriteToFile(outputPath)EnableAll walks every signature field in the AcroForm and takes the chain from
each signature dictionary. The certChain argument is additive: certificates the
signature does not carry, typically the issuing CA, which is what the optional
PEM file in the example supplies. Where the chain still has a gap, the client
downloads the issuer from the certificate’s IssuingCertificateURL, then requests
OCSP and CRL data for each certificate that advertises a responder.
If an existing DSS is present, NewLTV picks it up from the reader and extends
it rather than replacing it.
Limitations
SkipExisting defaults to true, so a signature that already has a VRI entry is
left alone. Set it to false on the LTV value to refresh the data.
Failures fetching revocation data are not failures. A timed-out OCSP request, an
unreachable CRL server or a missing issuer certificate is logged at debug level
and skipped, and EnableAll still returns nil. The output can be an LTV-enabled
file with no revocation data in it at all. Certificates that advertise no OCSP
responder and no CRL distribution point contribute nothing either. If you need
certainty, inspect the resulting DSS rather than trusting the absence of an
error.
The default HTTP timeout on CertClient, OCSPClient and CRLClient is 5
seconds per request. Raise it through ltv.OCSPClient.HTTPClient.Timeout and the
equivalents when talking to slow responders. Setting ltv.CRLClient or
ltv.OCSPClient to nil skips that class of data entirely.
Only self-signed certificates count as CA certificates for the purposes of skipping revocation lookups, so intermediates get OCSP and CRL requests too. The chain walk stops at the first self-signed certificate.
When no certificate can be assembled at all, the call fails with
ErrSignNoCertificates, “could not retrieve certificate chain”.
Running this on a document with no signature fields is a no-op that still writes
a new revision. A document that has a signature field with empty contents is
worse: EnableAll returns an “invalid signature field” error rather than
skipping it, despite logging that it intends to skip.
appender.Write and WriteToFile can only be called once per appender. Adding
another revision means a fresh reader and appender over the bytes just written.
Run the example
The example takes an already signed PDF and adds one revision containing the validation data. The optional third argument is a PEM file of issuer certificates, concatenated, for chains the signature does not carry in full.
git clone https://github.com/unidoc/unipdf-examples.git
cd unipdf-examples/signatures/ltv
go run pdf_ltv_enable_signed_file.go input.pdf output.pdf [extra_certs.pem]If this is your first time using UniPDF, follow the getting started guide to create an API key and set up your development environment.