Sign with PKCS12 File
A PKCS#12 file (.p12, .pfx) is a password-protected container holding a private
key together with its certificate, and it is what most certificate authorities hand
you. UniPDF has no PKCS#12 parser of its own, so the key and certificate come out
through golang.org/x/crypto/pkcs12, then feed the ordinary signing path.
Decoding the key pair
pfxData, err := os.ReadFile(p12Path)
if err != nil {
return err
}
priv, cert, err := pkcs12.Decode(pfxData, password)
if err != nil {
return err
}
handler, err := sighandler.NewAdobePKCS7Detached(priv.(*rsa.PrivateKey), cert)
if err != nil {
return err
}pkcs12.Decode returns the key as an interface{}, so it needs a type assertion.
NewAdobePKCS7Detached accepts *rsa.PrivateKey only. If the container holds an
EC key, use sighandler.NewAdobePKCS7DetachedEcdsa instead, which requires the
document to be PDF 2.0 or later.
Signing
The handler is the only part specific to PKCS#12. From there the sequence is the same for every signing method:
signature := model.NewPdfSignature(handler)
signature.SetName("Test Self Signed PDF")
signature.SetReason("TestSelfSignedPDF")
signature.SetDate(time.Now(), "")
if err := signature.Initialize(); err != nil {
return err
}
opts := annotator.NewSignatureFieldOpts()
opts.Rect = []float64{10, 25, 75, 60}
field, err := annotator.NewSignatureField(
signature,
[]*annotator.SignatureLine{
annotator.NewSignatureLine("Name", "John Doe"),
annotator.NewSignatureLine("Reason", "External signature test"),
},
opts,
)
if err != nil {
return err
}
field.T = core.MakeString("Self signed PDF")
if err := appender.Sign(1, field); err != nil {
return err
}
return appender.WriteToFile(outputPath)Initialize is what asks the handler to fill in /Filter, /SubFilter and to
reserve space for the signature bytes, and it fails with certificate must not be nil or privateKey must not be nil if the handler was built for validation rather
than signing. Everything else you want in the signature dictionary, SetName,
SetReason, SetLocation, SetDate, can be set on either side of it.
The second argument to SetDate is a Go time layout, not a format name. Leave it
empty to get the PDF date string readers expect, D:20060102150405-07'00'.
Limitations
pkcs12.Decode handles exactly one key and one certificate. A container that also
carries intermediate CA certificates fails with pkcs12: expected exactly two safe bags in the PFX PDU. Either export a P12 with just the leaf certificate, or convert
it with pkcs12.ToPEM and follow Sign with PEM File,
which embeds the whole chain.
The PKCS#7 handler reserves a fixed 8192 bytes for the signature contents, and the finished signature is copied into that space without a length check. Ordinary RSA signatures are far smaller, but a PKCS#7 blob carrying a long certificate chain or an embedded timestamp can exceed it, and the result is a truncated, invalid signature rather than an error.
model.NewPdfAppender needs the reader’s source to implement both io.ReadSeeker
and io.ReaderAt, since incremental signing rewrites over the original bytes.
*os.File and *bytes.Reader both qualify; a plain io.Reader wrapper does not,
and the appender returns an error saying so. The appender can also only be written
once, so sign, write, and reopen if you need a second signature.
Run the example
The example takes the P12 file and its password on the command line, decodes them, and puts one visible signature on page 1.
git clone https://github.com/unidoc/unipdf-examples.git
cd unipdf-examples/signatures
go run pdf_sign_pkcs12.go <FILE.p12> <PASSWORD> <IN.pdf> <OUT.pdf>If this is your first time using UniPDF, follow the getting started guide to create an API key and set up your development environment.