Signature
Signing a PDF in UniPDF is an incremental update. model.PdfAppender copies the
original bytes through untouched and appends a new revision containing the signature,
so the digest the signature covers stays valid and any earlier signature keeps
verifying. That is the reason signing never goes through PdfWriter: a writer rebuilds
the file, which invalidates every signature already in it.
Four objects are involved, and it helps to know which one owns what.
appender, err := model.NewPdfAppender(reader)
handler, err := sighandler.NewAdobePKCS7Detached(privateKey, cert)
signature := model.NewPdfSignature(handler)
signature.SetName("Jane Doe")
signature.SetReason("Approval")
signature.SetDate(time.Now(), "")
if err := signature.Initialize(); err != nil {
return err
}
field, err := annotator.NewSignatureField(signature, lines, opts)
field.T = core.MakeString("Approval signature")
if err := appender.Sign(1, field); err != nil {
return err
}
return appender.WriteToFile(outputPath)The handler holds the key and decides the cryptography: which /SubFilter is written,
how the digest is computed, and how much space /Contents needs. The signature
dictionary holds the metadata a reader displays, name, reason, location and date, plus
the certificate chain in /Cert for the handlers that use it. The field is the visible
part: a widget annotation with an appearance stream, a rectangle and a page. The
appender ties them together and produces the bytes.
Ordering matters in two places. signature.Initialize() must run before the appender
writes, because that is what asks the handler to reserve space for the signature, and
it is where a handler built without a private key fails. And PdfAppender.Write can
only be called once, so a second signature means writing, reopening the result, and
starting again.
Choosing a handler
| Handler | /SubFilter | Use it for |
|---|---|---|
sighandler.NewAdobePKCS7Detached | adbe.pkcs7.detached | The default choice. The signing certificate and signing time travel inside the PKCS#7 blob. |
sighandler.NewAdobePKCS7DetachedEcdsa | adbe.pkcs7.detached | An EC key. Defined by PDF 2.0; see ECDSA. |
sighandler.NewAdobeX509RSASHA1 | adbe.x509.rsa_sha1 | Older readers, or when the chain has to sit in /Cert. Defaults to SHA-1. |
sighandler.NewAdobeX509RSASHA1CustomWithOpts | adbe.x509.rsa_sha1 | Signing by a remote service or an HSM, through a SignFunc. Set Algorithm for SHA-256; the default is SHA-1. |
sighandler.NewEtsiPAdESLevelB and friends | ETSI.CAdES.detached | PAdES baseline conformance, up to LTA. |
sighandler.NewDocTimeStampWithOpts | ETSI.RFC3161 | A document timestamp rather than an identity signature. Set SignatureSize to fit the TSA’s token. |
sighandler.NewDocMDPHandler | wraps another | Certifying a document with a modification policy. |
Every constructor also accepts nil for the key and certificate, which produces a
validation-only handler. PdfReader.ValidateSignatures picks the first handler in the
list whose /Filter and /SubFilter match, so order the list from most specific to
least.
Appearance
A signature does not have to be visible. Building the field with
model.NewPdfFieldSignature and a zero-sized Rect skips the appearance entirely,
which is what a document timestamp wants. When it should be visible,
annotator.NewSignatureField builds the
appearance stream from a slice of annotator.SignatureLine and a
annotator.SignatureFieldOpts, and the styling is entirely independent of the
signature: nothing in the box is read back from the signature dictionary.
PdfAppender.SignWithAppearance is the alternative that draws the box from the
signature’s name, date, reason and location.
One PdfSignature can back several fields on several pages. Field names must still be
unique, and Sign only auto-names by page number, so set field.T yourself whenever a
document gets more than one signature widget.
Validation
PdfReader.ValidateSignatures checks integrity only: the bytes match the signature
under the certificate embedded in the file. It does not say who signed, and a
self-signed certificate under any name passes. To decide whether the signer is
someone you trust, call ValidateSignaturesWithOpts with your own trust roots. See
Validating Signer Trust and Revocation.
Neither check says how much of the file was signed. Content appended after a signature leaves it intact and trusted, so check the coverage fields on the result too. See Checking What a Signature Covers.
Where to look
The guides are grouped the same way as the signature examples, one section per folder.
| Section | Covers |
|---|---|
| Signing | Signing with a key you hold: PKCS12, PEM chains, ECDSA keys, generated keys, timestamps, new pages, second signatures, encrypted files and reading back earlier revisions. |
| Appearance | The visible signature box, with text, an image, or text generated from the signature. |
| External signing | Keys you do not hold: a remote service, AWS KMS, Google Cloud KMS, GlobalSign DSS and HSMs. |
| PAdES | The PAdES baseline levels, B-B through B-LTA. |
| DocMDP | Certifying a document with a modification policy. |
| Validation | Integrity checks, signer trust and revocation, ByteRange coverage, and timestamps. |
| Long-term validation | Embedding certificates, OCSP responses and CRLs so signatures stay verifiable. |