Skip to content
Sign with PEM File

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.NewAdobePKCS7DetachedChain travels inside the PKCS#7 blob; /Cert is not used.
sighandler.NewAdobeX509RSASHA1Signing certificate only, written to /Cert by Initialize.
sighandler.NewAdobeX509RSASHA1CustomSame, 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 = 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

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

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