Skip to content
Signing a PAdES B-LT Signature

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.

View the full source
Last updated on