Sign with PEM File
When your certificate arrives as PEM text, the private key and the certificate chain
are separate files and you parse them with the standard library. The interesting part
is the chain: a verifier needs the intermediate certificates to build a path from
your signing certificate to a trusted root, and the adbe.x509.rsa_sha1 signature
dictionary carries them in its /Cert entry.
| Handler | Chain support |
|---|---|
sighandler.NewAdobePKCS7Detached | Chain travels inside the PKCS#7 blob; /Cert is not used. |
sighandler.NewAdobeX509RSASHA1 | Signing certificate only, written to /Cert by Initialize. |
sighandler.NewAdobeX509RSASHA1Custom | Same, but you supply the signing function and can replace /Cert with the chain. |
Embedding the chain
Initialize sets /Cert to the signing certificate on its own, so the chain has to
be assigned afterwards. Do it before and it gets overwritten silently:
handler, err := sighandler.NewAdobeX509RSASHA1Custom(signingCert, signFunc)
if err != nil {
return err
}
signature := model.NewPdfSignature(handler)
if err := signature.Initialize(); err != nil {
return err
}
// Must come after Initialize, which sets Cert to signingCert alone.
signature.Cert = pdfCertspdfCerts is a *core.PdfObjectArray of raw DER strings, one per certificate, and
the first element has to be the signing certificate. The loadCertificates helper in
the example builds it by walking the PEM file with pem.Decode until nothing is
left, appending core.MakeString(string(cert.Raw)) for each block.
The signing function
NewAdobeX509RSASHA1Custom takes a sighandler.SignFunc rather than a private key,
which is what makes the certificate and the key independent:
signFunc := func(sig *model.PdfSignature, digest model.Hasher) ([]byte, error) {
h, ok := digest.(hash.Hash)
if !ok {
return nil, errors.New("hash type error")
}
return privateKey.Sign(rand.Reader, h.Sum(nil), crypto.SHA1)
}The digest handed to the function is a hash.Hash for this handler, already fed with
the bytes of the document. Whatever you return is wrapped in an ASN.1 octet string
and written to /Contents.
Limitations
This handler defaults to SHA-1, both for the digest it builds and for the hash it
assumes when validating. Signing with a stronger algorithm means setting Algorithm
in sighandler.AdobeX509RSASHA1Opts and hashing with the same algorithm inside your
sign function; the example passes crypto.SHA1 to privateKey.Sign to match the
default.
NewAdobeX509RSASHA1Custom calls your sign function twice per signature. The first
call happens inside Initialize, purely to measure how much space /Contents needs.
That is harmless for a local key, but with a remote signing service it doubles the
requests. The EstimateSize option on AdobeX509RSASHA1CustomWithOpts derives the
size from the certificate’s public key modulus instead, and skips the extra call:
handler, err := sighandler.NewAdobeX509RSASHA1CustomWithOpts(signingCert, signFunc,
&sighandler.AdobeX509RSASHA1Opts{EstimateSize: true, Algorithm: crypto.SHA256})Initialize returns certificate must not be nil, and
must provide either a private key or a signing function if the handler was built
for validation. The example’s loadPrivateKey uses x509.ParsePKCS1PrivateKey, so a
key in PKCS#8 form (BEGIN PRIVATE KEY rather than BEGIN RSA PRIVATE KEY) needs
x509.ParsePKCS8PrivateKey instead.
Run the example
sign does the work: it builds the custom handler, initializes the signature,
replaces /Cert with the chain, and puts one visible appearance on page 1. The
example also works when the PEM file holds only the signing certificate.
git clone https://github.com/unidoc/unipdf-examples.git
cd unipdf-examples/signatures
go run pdf_sign_pem_multicert.go in.pdf out.pdf certs.pem key.pemIf this is your first time using UniPDF, follow the getting started guide to create an API key and set up your development environment.