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. Chain and signing time travel inside the PKCS#7 blob. |
sighandler.NewAdobePKCS7DetachedEcdsa | adbe.pkcs7.detached | An EC key. Needs PDF 2.0 or later. |
sighandler.NewAdobeX509RSASHA1 | adbe.x509.rsa_sha1 | Older readers, or when the chain has to sit in /Cert. Defaults to SHA-1. |
sighandler.NewAdobeX509RSASHA1Custom | adbe.x509.rsa_sha1 | Signing by a remote service or an HSM, through a SignFunc. |
sighandler.NewEtsiPAdESLevelB and friends | ETSI.CAdES.detached | PAdES baseline conformance, up to LTA. |
sighandler.NewDocTimeStamp | ETSI.RFC3161 | A document timestamp rather than an identity signature. |
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.
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.
Where to look
Signing with a key you hold:
| Guide | Covers |
|---|---|
| PKCS12 file | Key and certificate from a .p12 or .pfx container. |
| PEM file | PEM key plus a certificate chain embedded in /Cert. |
| Generated key pair | A self-signed certificate made at runtime, for testing. |
The visible appearance:
| Guide | Covers |
|---|---|
| Appearance field | Position, font, colors, borders, several appearances per signature. |
| Image appearance | A signature image and a watermark inside the box. |
| New page | Appending a page to sign, which takes two passes. |
| Multiple signature fields | Signing an already-signed document again. |
Encryption, validation and revisions:
| Guide | Covers |
|---|---|
| Sign and encrypt | Signing a password-protected document. |
| Validate a signature | ValidateSignatures, and what the result flags mean. |
| Get a revision | Reading back an earlier revision of an updated file. |
Signing with a key you do not hold:
| Guide | Covers |
|---|---|
| External services | The SignFunc pattern for signing elsewhere. |
| AWS KMS | Keys held in AWS Key Management Service. |
| Google Cloud KMS | Keys held in Google Cloud KMS. |
| GlobalSign DSS | GlobalSign’s Digital Signing Service. |
| HSM via PKCS11 | A hardware security module through PKCS#11. |
Certifying a document with DocMDP:
| Guide | Covers |
|---|---|
| DocMDP restriction | Certifying with a modification policy. |
| DocMDP with valid changes | Changes the policy permits. |
| DocMDP with invalid changes | Changes that break the certification. |
Timestamps:
| Guide | Covers |
|---|---|
| Add a timestamp | A document timestamp from an RFC 3161 server. |
| Validate a timestamp | Checking a timestamp token. |
PAdES:
| Guide | Covers |
|---|---|
| PAdES baseline B | The B-B baseline profile. |
| PAdES baseline T | B-B plus a signature timestamp. |
| PAdES baseline LT | B-T plus validation data in the DSS. |
| PAdES baseline LTA | B-LT plus archival timestamps. |
| Validate a PAdES signature | Validating against the baseline profiles. |
Long-term validation:
| Guide | Covers |
|---|---|
| LTV in one revision | Signature and validation data in a single revision. |
| LTV on a signed file | Adding validation data to a document already signed. |
| LTV in a second revision | Validation data appended separately. |
| LTV with a timestamp | A second revision carrying validation data and a timestamp. |