Sign with PEM File

This guide will explain the process of signing a PDF file using a certificate chain and a private key that are extracted from PEM files.

Before you begin

You should get your API key from your UniCloud account.

If this is your first time using UniPDF SDK, follow this guide to set up a local development environment.

Project setup

Clone the project repository

In your terminal, clone examples repository using the following command: It contains the Go code we will be using for this guide.

git clone https://github.com/unidoc/unipdf-examples.git

Then navigate to the signatures folder in the unipdf-examples directory.

cd unipdf-examples/signatures

Configure environment variables

Configure your license key using the following command: Replace the UNIDOC_LICENSE_API_KEY with your API credentials from your UniCloud account.

Linux/Mac

export UNIDOC_LICENSE_API_KEY=PUT_YOUR_API_KEY_HERE

Windows

set UNIDOC_LICENSE_API_KEY=PUT_YOUR_API_KEY_HERE

How it works

/*
* This example showcases signing a PDF file using a certificate chain and
* a private key, both extracted from PEM files.
* The first certificate in the chain is actually used for signing the input
* PDF file, while the entire chain is embedded in the generated PDF signature
* in order to validate its authenticity.
* The example also works if the certificate file contains only the signing
* certificate.
*
* $ ./pdf_sign_pem_multicert <INPUT_PDF_PATH> <OUTPUT_PDF_PATH> <CERTIFICATE_PATH> <PRIVATE_KEY_PATH>
*
* Example: ./pdf_sign_pem_multicert in.pdf out.pdf certs.pem key.pem
*/
package main
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"hash"
"io/ioutil"
"log"
"os"
"time"
"github.com/unidoc/unipdf/v3/annotator"
"github.com/unidoc/unipdf/v3/common/license"
"github.com/unidoc/unipdf/v3/core"
"github.com/unidoc/unipdf/v3/model"
"github.com/unidoc/unipdf/v3/model/sighandler"
)
func init() {
// Make sure to load your metered License API key prior to using the library.
// If you need a key, you can sign up and create a free one at https://cloud.unidoc.io
err := license.SetMeteredKey(os.Getenv(`UNIDOC_LICENSE_API_KEY`))
if err != nil {
panic(err)
}
}
const (
usage = "Usage: %s INPUT_PDF_PATH OUTPUT_PDF_PATH CERTS_PATH PRIVATE_KEY_PATH\n"
)
func main() {
args := os.Args
if len(args) < 5 {
fmt.Printf(usage, os.Args[0])
return
}
inputPath, outputPath := args[1], args[2]
certPath, privateKeyPath := args[3], args[4]
// Load signing certificate and certificate chain as a PDF array object.
// The signing certificate is used to sign the input PDF file.
// The PDF certificate chain (which includes the signing certificate) will
// be embedded in the generated PDF signature.
signingCert, pdfCerts, err := loadCertificates(certPath)
if err != nil {
log.Fatal(err)
}
// Load private key.
privateKey, err := loadPrivateKey(privateKeyPath)
if err != nil {
log.Fatal(err)
}
// Sign input file and write output file.
err = sign(inputPath, outputPath, signingCert, privateKey, pdfCerts)
if err != nil {
log.Fatal(err)
}
log.Printf("PDF file successfully signed. Output path: %s\n", outputPath)
}
func loadPrivateKey(privateKeyPath string) (*rsa.PrivateKey, error) {
// Read private key file contents.
privateKeyData, err := ioutil.ReadFile(privateKeyPath)
if err != nil {
log.Fatal(err)
}
// Decode PEM block.
block, _ := pem.Decode(privateKeyData)
// Parse private key data.
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
return privateKey, nil
}
func loadCertificates(certPath string) (*x509.Certificate, *core.PdfObjectArray, error) {
// Read certificate file contents.
certData, err := ioutil.ReadFile(certPath)
if err != nil {
return nil, nil, err
}
parseCert := func(data []byte) (*x509.Certificate, []byte, error) {
// Decode PEM block.
block, rest := pem.Decode(data)
// Parse certificate.
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, nil, err
}
return cert, rest, nil
}
// Create PDF array object which will contain the certificate chain data,
// loaded from the PEM file. The first element of the array must be the
// signing certificate. The rest of the certificate chain is used for
// validating the authenticity of the signing certificate.
pdfCerts := core.MakeArray()
// Parse signing certificate.
signingCert, pemUnparsedData, err := parseCert(certData)
if err != nil {
return nil, nil, err
}
pdfCerts.Append(core.MakeString(string(signingCert.Raw)))
// Parse the rest of the certificates contained in the PEM file,
// if any, and add them to the PDF certificates array.
for len(pemUnparsedData) != 0 {
cert, rest, err := parseCert(pemUnparsedData)
if err != nil {
return nil, nil, err
}
pdfCerts.Append(core.MakeString(string(cert.Raw)))
pemUnparsedData = rest
}
return signingCert, pdfCerts, nil
}
func sign(inputPath, outputPath string, signingCert *x509.Certificate,
privateKey *rsa.PrivateKey, pdfCerts *core.PdfObjectArray) error {
// Create signature function.
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)
}
// Create custom signature handler.
handler, err := sighandler.NewAdobeX509RSASHA1Custom(signingCert, signFunc)
if err != nil {
return err
}
// Create and initialize signature.
signature := model.NewPdfSignature(handler)
if err := signature.Initialize(); err != nil {
return err
}
// Set signature fields.
signature.SetName("Test PEM Multicert Signature")
signature.SetReason("Test_PEM_Multicert_Signature")
signature.SetDate(time.Now(), "")
// Set signature certificate chain.
signature.Cert = pdfCerts
// Create signature field and appearance.
opts := annotator.NewSignatureFieldOpts()
opts.FontSize = 10
opts.Rect = []float64{10, 25, 75, 60}
sigField, err := annotator.NewSignatureField(
signature,
[]*annotator.SignatureLine{
annotator.NewSignatureLine("Name", "John Doe"),
annotator.NewSignatureLine("Date", "2020.03.27"),
annotator.NewSignatureLine("Reason", "UniPDF Signature Test"),
},
opts,
)
sigField.T = core.MakeString("PEM Multicert signature")
// Open input file.
file, err := os.Open(inputPath)
if err != nil {
log.Fatalf("Fail: %v\n", err)
}
defer file.Close()
// Create reader.
reader, err := model.NewPdfReader(file)
if err != nil {
return err
}
// Create appender.
appender, err := model.NewPdfAppender(reader)
if err != nil {
return err
}
// Sign input PDF file.
// The signature appearance is placed on the first page of the output PDF file.
// See https://github.com/unidoc/unipdf-examples/blob/master/signatures/pdf_sign_appearance.go
// for adding multiple signature appearance annotations, on multiple pages.
if err = appender.Sign(1, sigField); err != nil {
return err
}
// Write output file.
return appender.WriteToFile(outputPath)
}

The import section imports the UniPDF packages and other necessary Go libraries. The init function loads the metered license key form the system environment and sets the license using icense.SetMeteredKey(os.Getenv(`UNIDOC_LICENSE_API_KEY`)).

The main functions starts in line 60. In this function the inputPath, outputPath,certPath and privateKeyPath are parsed from the command line arguments in line 51-57.

The signing certificate and the certificate chains are loaded using loadCertificates(certPath). This function returns the certificate as x509.Certificate object and the certificate chain as a PDF array object. The private key is loaded using loadPrivateKey(privateKeyPath) from the provided private key path. Then using sign(inputPath, outputPath, signingCert, privateKey, pdfCerts) the input file is signed and written to output file.

The loadPrivateKey function loads the private key from file and returns it as rsa.PrivateKey. The function returns an error if the loading process fails.

The loadCertificates function defined in lines 102-148 loads the certificate from the input path provided in the function parameter. It returns x509.Certificate and core.PdfObjectArray types which are the signing certificate and the certificate chain respectively.

The sign function signs the input PDF file using the singing certificate and embeds the certificate chain, including the signing certificate itself, inside the generated PDF signature. In this function in lines 153-160, an anonymous signature function is defined and assigned to signFunc. This signing function is then used in the call to NewAdobeX509RSASHA1Custom method to get a handler as follows:

handler, err := sighandler.NewAdobeX509RSASHA1Custom(signingCert, signFunc)

A new PdfSignature object is created and its parameters are set as follows.

signature := model.NewPdfSignature(handler)
if err := signature.Initialize(); err != nil {
  return err
}

// Set signature fields.
signature.SetName("Test PEM Multicert Signature")
signature.SetReason("Test_PEM_Multicert_Signature")
signature.SetDate(time.Now(), "")

// Set signature certificate chain.
signature.Cert = pdfCerts

In lines 183-196, signature field and appearance are created by leveraging the functions in the annotator package.

Then the document is signed in line 221, using appender.Sign(1, sigField) method. Finally, the document is written to file and the result of the write process is returned from the function.

Run the code

Run the code using the following command:

go run pdf_sign_pem_multicert.go in.pdf out.pdf certs.pem key.pem

Got any Questions?

We're here to help you.