Skip to content
Sign with PKCS12 File

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.

View the full source
Last updated on