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 software.sslmate.com/src/go-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.DecodeChain(pfxData, password)
if err != nil {
return err
}
rsaKey, ok := priv.(*rsa.PrivateKey)
if !ok {
return fmt.Errorf("private key is %T; only RSA keys are supported", priv)
}
handler, err := sighandler.NewAdobePKCS7Detached(rsaKey, cert)
if err != nil {
return err
}DecodeChain returns the key as an interface{}, the signing certificate, and any CA
certificates stored alongside it. It reads files with or without a CA chain, including
the AES-encrypted ones OpenSSL 3 writes by default. NewAdobePKCS7Detached accepts
*rsa.PrivateKey only, so the example checks the key type and exits with
private key is *ecdsa.PrivateKey; only RSA keys are supported when the container
holds an EC key, and points to the ECDSA example. That key goes to sighandler.NewAdobePKCS7DetachedEcdsa instead. See
Sign with an ECDSA Key.
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.FontSize = 10
opts.Rect = []float64{10, 25, 75, 60}
field, err := annotator.NewSignatureField(
signature,
[]*annotator.SignatureLine{
annotator.NewSignatureLine("Name", "John Doe"),
annotator.NewSignatureLine("Date", time.Now().Format("2006.01.02")),
annotator.NewSignatureLine("Reason", "PKCS12 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
DecodeChain expects one private key. It takes the first certificate in the file as
the signing certificate and the rest as its CA chain. The example discards the CA
certificates, and NewAdobePKCS7Detached puts only the signing certificate in the
PKCS#7 package, so a verifier has to find the intermediates elsewhere. To ship the
chain with the signature, follow Sign with PEM File, which writes every
certificate to /Cert.
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/signing
go run pdf_sign_pkcs12.go <FILE.p12> <PASSWORD> <INPUT_PDF_PATH> <OUTPUT_PDF_PATH>If this is your first time using UniPDF, follow the getting started guide to create an API key and set up your development environment.