Sign with Google Cloud KMS Service

This guide will explain the process of signing a PDF file using an external signing service Google Cloud KMS. A Google Cloud KMS KEY with Asymmetric sign Purpose is necessary to achieve this.

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

The next section will explain the example code.

How it works

The import section of the example code imports the necessary UniPDF packages and other Go libraries. The init function defined in lines 43-50, initializes the program by setting your metered license key to authenticate your library request.

The main function is defined in lines 61-112. The inputPath, outputPath, gcCredentialsPath and keyName are parsed from the command line arguments in lines 62-70. The code in lines 73-81, generates a PDF file signed with empty signature. Then the byte range of the signature is parsed using parseByteRange(signature.ByteRange) in line 84. The getExternalSignatureAndSign function simulates the process of sending the PDF buffer to a signing device or signing web service and getting back the signature. The PDF signed bytes and signature are obtained from this function. The external signature is applied to the PDF data buffer using the following piece of code:

sigBytes := make([]byte, sigLen)
copy(sigBytes, signatureData)

sig := core.MakeHexString(string(sigBytes)).WriteString()
copy(pdfData[byteRange[1]:byteRange[2]], []byte(sig))

Finally, the signed document is written to the output path.

The function generateSignedFile returns a signed version of the input PDF file using the specified signature handler. In line 130, a new PDF Appender is created using model.NewPdfAppender(reader) from the reader. Then a new signature is created in lines 136-139 and gets initialized by calling signature.Initialize().

A signature field and appearance is created using:

opts := annotator.NewSignatureFieldOpts()
opts.FontSize = 10
opts.Rect = []float64{10, 25, 75, 60}

field, err := annotator.NewSignatureField(
  signature,
  []*annotator.SignatureLine{
    annotator.NewSignatureLine("Name", "John Doe"),
    annotator.NewSignatureLine("Date", "2023.01.01"),
    annotator.NewSignatureLine("Reason", "GC KMS Sing Test"),
  },
  opts,
)
if err != nil {
  return nil, nil, err
}

field.T = core.MakeString("External signature")

Then the document is signed using appender.Sign(1, field). Finally, the buffer that contains the signed document and the signature object are returned from the function in line 175.

The getExternalSignatureAndSign function starting in line 180 simulates an external service which signs the specified PDF file and returns its signed bytes and signature. In this function in line 181 an externalSigner is obtained by calling GcKmsExternalSigner(credPath, keyName). A certificate template is created in line 190-201 which is used to create a certificate data. This certificate data is parsed into a x509.Certificate object in line 209. Then using generateSignedFile(inputPath, handler) the signed PDF bytes and the signature is obtained 221. Finally, the signature content bytes and PDF bytes are returned from the function.

The parseByteRange function parses the ByteRange value of the signature field.

A CryptoSigner type is defined in lines 263-268. The Public and Sign methods of this type are part of the crypto.Signer interface implementation. The NewCryptoSigner function returns a new CryptoSigner.

The externalSigner type is defined in lines 382-395. The GcKmsExternalSigner function defined in liens 399-417, is used to wrap externalSigner to model.SignatureHandler. The InitSignature method which initializes the model.PdfSignature parameters is part of the model.SignatureHandler interface implementation.

The Sign method tries to sign the digest based on the signature dictionary sig and applies the signature data to the signature dictionary’s Contents field. It returns an error in case of a failure. IsApplicable method checks if the signature handler is applicable for the PdfSignature. The NewDigest method returns a new model.Hasher digest data. The Validate method, as can be guessed from its name, validates the PdfSignature. These four methods are part of the model.SignatureHandler interface.

The getPublicKey() method defined in lines 477-479, returns the public key of the externalSigner. toSigHandler() casts the externalSigner to model.SignatureHandler type.

Run the code

Run the code using the following command:

go run pdf_sign_external_aws_kms.go <INPUT_PDF_PATH> <OUTPUT_PDF_PATH> <KEY_ID>

Got any Questions?

We're here to help you.