Skip to content
Signing a PAdES B-LTA Signature

Signing a PAdES B-LTA Signature

B-LTA is a B-LT document with a document timestamp appended on top. The document timestamp covers the whole file, validation data included, so the DSS contents are themselves time-anchored and cannot be swapped out later. It is also renewable: appending a fresh document timestamp before the current one’s algorithms weaken extends the document’s life without touching the original signature.

There is no LevelLTA in the SDK. sighandler.EtsiPAdESLevel stops at LevelLT, and B-LTA is built from a B-LT signature plus a NewDocTimeStamp revision.

Three revisions

The example writes three revisions in order, each one appended over the bytes of the last:

RevisionWritten withCarries
1NewEtsiPAdESLevelLT handlerThe signature.
2appender2.SetDSS(appender.GetDSS())The DSS, including the VRI entry for the signature.
3NewDocTimeStamp handlerThe document timestamp over everything above.

Revisions 1 and 2 are the ordinary B-LT flow. The order matters for revision 3: the timestamp has to come after the DSS revision, or the validation data falls outside what it covers.

Adding the document timestamp

appender3, err := model.NewPdfAppender(pdf3)
if err != nil {
    return err
}

handler, err := sighandler.NewDocTimeStamp(timestampServerURL, crypto.SHA512)
if err != nil {
    return err
}

signature := model.NewPdfSignature(handler)
signature.SetName("Test Signature")
signature.SetDate(time.Now(), "")

if err := signature.Initialize(); err != nil {
    return err
}

Initialize sets the signature type to DocTimeStamp and the subfilter to ETSI.RFC3161, which is what distinguishes it from the ETSI.CAdES.detached signature underneath. Supported hash algorithms are SHA-1, SHA-256, SHA-384 and SHA-512.

A document timestamp has no signer to display, so the example gives its field an empty rectangle:

opts := annotator.NewSignatureFieldOpts()
opts.Rect = []float64{0, 0, 0, 0}

The signature lines passed alongside are then never drawn.

Limitations

NewDocTimeStamp makes two requests to the TSA. It has no size estimate for the contents, so Initialize performs a real timestamp request to measure one. Pass a size instead to avoid the extra round trip:

handler, err := sighandler.NewDocTimeStampWithOpts(timestampServerURL, crypto.SHA512,
    &sighandler.DocTimeStampOpts{SignatureSize: 8192})

If the estimate turns out to be too small, signing fails with model.ErrSignNotEnoughSpace rather than producing a truncated token. The default, when SignatureSize is left at zero, is 4192 bytes. DocTimeStampOpts.Client takes a sigutil.TimestampClient for TSAs needing authentication or a longer timeout.

Validating a B-LTA file needs two handlers, because the two signatures have different subfilters. A PAdES handler alone will not match the document timestamp, and ValidateSignatures reports handler not set for it. See Validating PAdES signatures.

Each appender can only be written once, which is why the example creates a new reader and appender per revision rather than reusing one.

Run the example

The example runs all three revisions, keeping the first two in memory and writing only the final file. Read main from the appender.Write(buffer) call onward to see the sequence.

git clone https://github.com/unidoc/unipdf-examples.git
cd unipdf-examples/signatures
go run pdf_sign_pades_b_lta.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