How to sign and timestamp a PDF
A timestamp from a trusted authority records when the signature existed,
independently of the clock on the signing machine. signature.SetDate only
writes a date into the signature dictionary, which the signer controls and
nobody can verify. A TSA timestamp is what proves the document was signed before
a given moment, which is what matters once the signing certificate expires.
UniPDF offers two ways to attach one, and they are not interchangeable.
A document timestamp is a second signature over the whole file, added in its own
revision. Sign normally, write the result, then reopen that output and append a
DocTimeStamp:
appender, err := model.NewPdfAppender(pdfReader)
if err != nil {
return err
}
handler, err := sighandler.NewDocTimeStamp("https://freetsa.org/tsr", crypto.SHA512)
if err != nil {
return err
}
signature := model.NewPdfSignature(handler)
signature.SetName("Test Appender")
signature.SetDate(time.Now(), "")
if err := signature.Initialize(); err != nil {
return err
}
sigField := model.NewPdfFieldSignature(signature)
sigField.T = core.MakeString("Signature1")
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 err
}
return appender.WriteToFile(outputPath)The zero rectangle keeps the timestamp invisible, which is normal; a document timestamp is not something a reader needs to see.
The alternative is a signature timestamp embedded in the signature itself, which
is PAdES B-T. sighandler.NewEtsiPAdESLevelT takes the TSA URL as its last
argument and handles the request during signing, so there is no second pass.
Pick this one if you are targeting the PAdES baseline profiles, since B-LT and
B-LTA build on top of it. Pick the document timestamp if you are adding a
timestamp to a document that is already signed, or working toward B-LTA, where a
document timestamp is applied over a B-LT signature.
Either way the handler contacts the TSA over the network while you sign, so the
call can fail or hang. NewDocTimeStamp makes a mock signing call first in
order to estimate how much space to reserve, which means two round trips to the
server. sighandler.NewDocTimeStampWithOpts skips that by letting you state the
size up front:
handler, err := sighandler.NewDocTimeStampWithOpts(serverURL, crypto.SHA512,
&sighandler.DocTimeStampOpts{SignatureSize: 6000})It defaults to 4192 bytes if you pass zero or less, and signing fails with
model.ErrSignNotEnoughSpace when the real timestamp does not fit. Overshoot;
the wasted bytes cost nothing. DocTimeStampOpts.Client takes a
*sigutil.TimestampClient if you need a longer HTTP timeout than the default.
The related sibling page explains
why the handler is called twice.
Supported hash algorithms are crypto.SHA1, crypto.SHA256, crypto.SHA384
and crypto.SHA512. To validate a timestamp later, build the same handler with
an empty URL and a zero algorithm, sighandler.NewDocTimeStamp("", 0), and pass
it to reader.ValidateSignatures.
To check out how you can sign documents by using your digital certificates and timestamp them, you can check out this user guide. In the example, we are digitally signing and timestamping a document by using a PKCS12 file.
See also Validate a Timestamp Signature and Signing a PAdES B-T Signature.