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 | Signing certificate only, inside the PKCS#7 package; /Cert is not used. |
sighandler.NewAdobeX509RSASHA1 | Signing certificate only, written to /Cert by Initialize. |
sighandler.NewAdobeX509RSASHA1CustomWithOpts | 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.NewAdobeX509RSASHA1CustomWithOpts(signingCert, signFunc,
&sighandler.AdobeX509RSASHA1Opts{Algorithm: crypto.SHA256})
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
NewAdobeX509RSASHA1CustomWithOpts 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.SHA256)
}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.
The handler builds that digest with the hash named in Algorithm, and your function
has to sign with the same one. Both say SHA-256 in the example. Leave Algorithm
unset and the digest is SHA-1, so privateKey.Sign with crypto.SHA256 rejects the
20-byte input with crypto/rsa: input must be hashed message and Initialize fails.
Whatever the adbe.x509.rsa_sha1 name says, a validator reads the hash back out of
the PKCS#1 v1.5 signature, so a SHA-256 signature validates with a plain
sighandler.NewAdobeX509RSASHA1(nil, nil).
Limitations
Unless EstimateSize is set, the handler 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, and the example leaves the
option off, but with a remote signing service it doubles the requests. EstimateSize
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 reads the PEM block type and parses
RSA PRIVATE KEY with x509.ParsePKCS1PrivateKey and PRIVATE KEY with
x509.ParsePKCS8PrivateKey. The handler signs with RSA only, so a PKCS#8 EC key
fails with private key is *ecdsa.PrivateKey; only RSA keys are supported, and any
other block type, EC PRIVATE KEY or ENCRYPTED PRIVATE KEY among them, with
unsupported private key PEM block.
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/signing
go run pdf_sign_pem_multicert.go <INPUT_PDF_PATH> <OUTPUT_PDF_PATH> <CERTIFICATE_PATH> <PRIVATE_KEY_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.