Skip to content
LTV Enable a Signed File

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 goesCallUsed for validating
Global DSS entriesEnableChain(chain)Any signature in the document.
A VRI entry, plus the global entriesEnable, EnableByName, EnableAllOne 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.

View the full source
Last updated on