Skip to content
How to sign a PDF with an external signing service

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.

Last updated on