Signing a PAdES B-LT Signature
B-LT is B-T plus the material a verifier would otherwise have to fetch from the internet: the certificate chain, OCSP responses and CRLs, gathered while signing and stored in the document’s DSS. That makes the document self-contained, which matters because OCSP responders and CRL distribution points go away long before archived documents do.
Two things separate the code from B-T. The handler takes the appender as an argument, since it writes into the appender’s DSS, and the output needs two write passes rather than one.
Signing at B-LT
appender, err := model.NewPdfAppender(reader)
if err != nil {
return err
}
timestampServerURL := "https://freetsa.org/tsr"
handler, err := sighandler.NewEtsiPAdESLevelLT(priv, cert, cacert, timestampServerURL, appender)
if err != nil {
return err
}The constructor reads appender.Reader.DSS, creating an empty one if the
document has none, and builds its hash maps so that validation data already in
the file is not duplicated. It also installs default sigutil clients for
certificate, OCSP and CRL retrieval; replace them with SetCertClient,
SetOCSPClient and SetCRLClient if those requests need a proxy or a longer
timeout.
Signature creation, field construction and appender.Sign(1, field) are
unchanged from B-B.
The second pass
buffer := bytes.NewBuffer(nil)
if err := appender.Write(buffer); err != nil {
return err
}
pdf2, err := model.NewPdfReader(bytes.NewReader(buffer.Bytes()))
if err != nil {
return err
}
appender2, err := model.NewPdfAppender(pdf2)
if err != nil {
return err
}
appender2.SetDSS(appender.GetDSS())
if err := appender2.WriteToFile(outputPath); err != nil {
return err
}The DSS holds validation data in two scopes: a global one usable for any signature in the file, and a per-signature VRI entry, which is the one PAdES wants. VRI entries are keyed on the uppercase hex SHA-1 of the finished signature contents, and the appender serializes the DSS before those contents exist. So the first pass produces the signed revision, and the second appends a revision carrying the completed DSS. Adding validation data in a later revision does not break the signature; for pre-2.0 documents the appender declares the ESIC and ADBE 1.7 extensions so that readers know to look there.
Skipping the second pass leaves you with a signature that is still valid but has no VRI entry, which is exactly the gap B-LT is supposed to close.
Limitations
Failed lookups are not errors. If the OCSP responder times out, the CRL
distribution point refuses the connection, or an issuer certificate cannot be
retrieved, the handler logs at debug level and moves on. A B-LT signature can
come out carrying no revocation data at all and no error will be returned, so
check the signed file rather than assuming. IsCrlFound and IsOcspFound on the
validation result are the quick way to do that.
OCSP requests are skipped for certificates the SDK identifies as CA certificates, and for any certificate whose issuer is not in the chain the handler managed to assemble. A chain that stops short, because an intermediate is neither supplied nor advertised, silently produces fewer OCSP responses.
The revocation data goes into the CMS as an Adobe revocation-info signed
attribute as well as into the DSS, and the handler grows the reserved Contents
space to fit it. That is why a B-LT signature is substantially larger than a B-T
one.
Signing at B-LT hits the network repeatedly. Initialize performs a full mock
signing pass to size the contents, which means the timestamp request, the
certificate chain build, and the OCSP and CRL lookups all run twice.
For long-term validation of a signature that already exists, or for LTV without
PAdES levels, use model.NewLTV and LTV.EnableChain instead. Those guides are
under Enabling LTV.
Run the example
The example signs page 1, writes the signed revision to a buffer, then copies the
DSS onto a second appender and writes the file. Everything is in main.
git clone https://github.com/unidoc/unipdf-examples.git
cd unipdf-examples/signatures
go run pdf_sign_pades_b_lt.go <FILE.PFX> <PASSWORD> <FILE.PEM> <INPUT_PDF_PATH> <OUTPUT_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.