Skip to content
LTV Enable with a Document Timestamp

LTV Enable with a Document Timestamp

Validation data added in a second revision is not covered by the signature, so nothing in the file proves when it was added or that it has not been swapped since. A document timestamp in that same revision fixes both: it is a signature by a trusted timestamp authority over everything written so far, including the DSS. That is the shape a PAdES B-LT signature has.

The example builds it in three revisions.

RevisionWritten byContents
1signFileThe signature.
2ltvEnableAndTimestampThe DSS for the signature, plus a document timestamp covering it.
3ltvEnableTimestampSigThe DSS for the timestamp signature. Optional.

Chaining the revisions

Each revision needs its own reader and appender, since an appender can only be written once. The bytes travel from one step to the next in memory:

signedBytes, err := signFile(inputPath, priv.(*rsa.PrivateKey), cert)
if err != nil {
    return err
}

signedBytes, err = ltvEnableAndTimestamp(bytes.NewReader(signedBytes), certChain)
if err != nil {
    return err
}

signedBytes, err = ltvEnableTimestampSig(bytes.NewReader(signedBytes))
if err != nil {
    return err
}

return os.WriteFile(outputPath, signedBytes, 0644)

The helpers take *bytes.Reader rather than io.ReadSeeker because model.NewPdfAppender requires a source that satisfies both io.ReadSeeker and io.ReaderAt.

The DSS and the timestamp together

Inside ltvEnableAndTimestamp, the DSS is filled in and the timestamp signature applied to the same appender, so one write produces both:

ltv, err := model.NewLTV(appender)
if err != nil {
    return nil, err
}

if err := ltv.EnableAll(certChain); err != nil {
    return nil, err
}

handler, err := sighandler.NewDocTimeStamp("https://freetsa.org/tsr", crypto.SHA512)
if err != nil {
    return nil, err
}

signature := model.NewPdfSignature(handler)
if err := signature.Initialize(); err != nil {
    return nil, err
}

sigField := model.NewPdfFieldSignature(signature)
sigField.T = core.MakeString("Test Sign Timestamp")
sigField.Rect = core.MakeArray(core.MakeInteger(0), core.MakeInteger(0),
    core.MakeInteger(0), core.MakeInteger(0))

if err = appender.Sign(1, sigField); err != nil {
    return nil, err
}

A document timestamp has no signer and nothing to show, so the field is built directly with model.NewPdfFieldSignature and given a zero rectangle rather than going through annotator.NewSignatureField.

LTV enabling the timestamp

The last revision calls ltv.EnableAll(nil). No chain is passed because there is nothing to add: a timestamp signature has the subfilter ETSI.RFC3161, and for that subfilter the certificate chain is read out of the PKCS7 token in the signature contents, which the TSA populates with its own certificates. The result keeps the timestamp verifiable after the TSA certificate expires, which is what distinguishes a B-LTA signature from a B-LT one.

This step is optional. Skip it and the document signature stays long-term verifiable; the timestamp over it does not.

Limitations

NewDocTimeStamp performs a mock Sign while initializing the signature in order to size the contents, so each timestamp costs two requests to the TSA. sighandler.NewDocTimeStampWithOpts with DocTimeStampOpts.SignatureSize skips the mock call; the default size is 4192 bytes, and a token larger than the reserved size fails with model.ErrSignNotEnoughSpace.

The supported hash algorithms are crypto.SHA1, crypto.SHA256, crypto.SHA384 and crypto.SHA512. Anything else fails when the response is parsed.

The example points at https://freetsa.org/tsr, a public test service. Each revision that timestamps makes a live HTTP request, so the example does not run offline.

Everything under LTV enable a signed file about skipped revocation lookups applies to both EnableAll calls here.

Run the example

signFile, ltvEnableAndTimestamp and ltvEnableTimestampSig map one to one onto the three revisions in the table above. The optional PEM argument supplies issuer certificates for the signing chain only; the timestamp chain comes from the token.

git clone https://github.com/unidoc/unipdf-examples.git
cd unipdf-examples/signatures/ltv
go run pdf_sign_ltv_timestamp_revision.go cert.p12 password 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