Skip to content

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.

HandlerChain support
sighandler.NewAdobePKCS7DetachedSigning certificate only, inside the PKCS#7 package; /Cert is not used.
sighandler.NewAdobeX509RSASHA1Signing certificate only, written to /Cert by Initialize.
sighandler.NewAdobeX509RSASHA1CustomWithOptsSame, 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 = pdfCerts

pdfCerts 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.

View the full source
Last updated on