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.
| Revision | Written by | Contents |
|---|---|---|
| 1 | signFile | The signature. |
| 2 | ltvEnableAndTimestamp | The DSS for the signature, plus a document timestamp covering it. |
| 3 | ltvEnableTimestampSig | The 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.