How to sign a PDF with an external signing service
Getting the green checkmark in Acrobat requires an AATL approved certificate, and those are normally held in hardware: a USB token, a smart card, or an HSM reached over PKCS#11. Operating that hardware yourself is often not practical, so the key stays with a third party and you send them something to sign.
The pattern is a placeholder pass followed by a byte splice. Sign with
NewEmptyAdobePKCS7Detached, which reserves space without producing a real
signature, then overwrite the reserved bytes with what the service returns:
handler, err := sighandler.NewEmptyAdobePKCS7Detached(8192)
if err != nil {
return err
}
// Sign as usual with this handler and write to a buffer.
pdfData, signature, err := generateSignedFile(inputPath, handler)
if err != nil {
return err
}
byteRange, err := parseByteRange(signature.ByteRange)
if err != nil {
return err
}
// Send pdfData to the signing device or service, get the signature back.
signatureData, err := getExternalSignature(pdfData)
if err != nil {
return err
}
sigBytes := make([]byte, 8192)
copy(sigBytes, signatureData)
copy(pdfData[byteRange[1]:byteRange[2]], core.MakeHexString(string(sigBytes)).Write())generateSignedFile, parseByteRange and getExternalSignature are helpers
defined in the example itself, not library calls. Only
signature.ByteRange, a *core.PdfObjectArray on model.PdfSignature, comes
from UniPDF.
The 8192 passed to NewEmptyAdobePKCS7Detached and the 8192 used for sigBytes
have to be the same number. That value is the reserved region, and the splice
writes a full-length padded buffer into it; a shorter buffer would leave stale
bytes behind and a longer one would run past the gap and corrupt the file. Make
it comfortably larger than any signature the service will return.
Splice into byteRange[1]:byteRange[2], the gap the ByteRange skips over. Those
are exactly the bytes excluded from the digest, which is why writing there does
not invalidate the signature you just computed. Nothing else in the file may
change after the digest is taken, so this is a byte-level patch on the buffer
rather than a re-write through the appender.
If your service exposes a signing callback instead of a detached blob, the other
route is sighandler.NewAdobeX509RSASHA1Custom, which takes a SignFunc that
UniPDF calls with the digest. That handler invokes your function twice by
default, once to size the signature; see
why the handler is called twice for the
option that avoids the extra call, which matters when every call is a billable
API request.
You can check out the user guide to learn how to use such a 3rd party service to work with UniPDF to send the hash to the 3rd party server, and then applying the signature to the final PDF.
There are worked guides for the common providers: AWS KMS, Google Cloud KMS, GlobalSign DSS and an HSM via PKCS11.