Skip to content
Signature

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/SubFilterUse it for
sighandler.NewAdobePKCS7Detachedadbe.pkcs7.detachedThe default choice. Chain and signing time travel inside the PKCS#7 blob.
sighandler.NewAdobePKCS7DetachedEcdsaadbe.pkcs7.detachedAn EC key. Needs PDF 2.0 or later.
sighandler.NewAdobeX509RSASHA1adbe.x509.rsa_sha1Older readers, or when the chain has to sit in /Cert. Defaults to SHA-1.
sighandler.NewAdobeX509RSASHA1Customadbe.x509.rsa_sha1Signing by a remote service or an HSM, through a SignFunc.
sighandler.NewEtsiPAdESLevelB and friendsETSI.CAdES.detachedPAdES baseline conformance, up to LTA.
sighandler.NewDocTimeStampETSI.RFC3161A document timestamp rather than an identity signature.
sighandler.NewDocMDPHandlerwraps anotherCertifying 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:

GuideCovers
PKCS12 fileKey and certificate from a .p12 or .pfx container.
PEM filePEM key plus a certificate chain embedded in /Cert.
Generated key pairA self-signed certificate made at runtime, for testing.

The visible appearance:

GuideCovers
Appearance fieldPosition, font, colors, borders, several appearances per signature.
Image appearanceA signature image and a watermark inside the box.
New pageAppending a page to sign, which takes two passes.
Multiple signature fieldsSigning an already-signed document again.

Encryption, validation and revisions:

GuideCovers
Sign and encryptSigning a password-protected document.
Validate a signatureValidateSignatures, and what the result flags mean.
Get a revisionReading back an earlier revision of an updated file.

Signing with a key you do not hold:

GuideCovers
External servicesThe SignFunc pattern for signing elsewhere.
AWS KMSKeys held in AWS Key Management Service.
Google Cloud KMSKeys held in Google Cloud KMS.
GlobalSign DSSGlobalSign’s Digital Signing Service.
HSM via PKCS11A hardware security module through PKCS#11.

Certifying a document with DocMDP:

GuideCovers
DocMDP restrictionCertifying with a modification policy.
DocMDP with valid changesChanges the policy permits.
DocMDP with invalid changesChanges that break the certification.

Timestamps:

GuideCovers
Add a timestampA document timestamp from an RFC 3161 server.
Validate a timestampChecking a timestamp token.

PAdES:

GuideCovers
PAdES baseline BThe B-B baseline profile.
PAdES baseline TB-B plus a signature timestamp.
PAdES baseline LTB-T plus validation data in the DSS.
PAdES baseline LTAB-LT plus archival timestamps.
Validate a PAdES signatureValidating against the baseline profiles.

Long-term validation:

GuideCovers
LTV in one revisionSignature and validation data in a single revision.
LTV on a signed fileAdding validation data to a document already signed.
LTV in a second revisionValidation data appended separately.
LTV with a timestampA second revision carrying validation data and a timestamp.
Last updated on