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:
| Revision | Written with | Carries |
|---|---|---|
| 1 | NewEtsiPAdESLevelLT handler | The signature. |
| 2 | appender2.SetDSS(appender.GetDSS()) | The DSS, including the VRI entry for the signature. |
| 3 | NewDocTimeStamp handler | The 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.