Skip to content
Sign with Google Cloud KMS

Sign with Google Cloud KMS

Signing with Google Cloud KMS follows the same shape as any other key that never leaves its host: a custom model.SignatureHandler assembles the PKCS7 package locally and hands the digest to a crypto.Signer that calls AsymmetricSign. The key needs the asymmetric sign purpose, and you address a specific key version, not the key:

projects/my-project/locations/us-east1/keyRings/my-key-ring/cryptoKeys/my-key/cryptoKeyVersions/123

Wiring up the client

client, err := kms.NewKeyManagementClient(ctx, gcOption.WithCredentialsFile(credPath))
if err != nil {
    return nil, err
}

extSigner := NewCryptoSigner(client, keyName, nil)

The signer implements Public() by fetching the key’s PEM from KMS and Sign() by sending the digest:

req := &kmspb.AsymmetricSignRequest{
    Name: cs.keyName,
    Digest: &kmspb.Digest{
        Digest: &kmspb.Digest_Sha256{Sha256: digest},
    },
    DigestCrc32C: wrapperspb.Int64(int64(crc32c(digest))),
}

The Digest_Sha256 variant fixes the hash at SHA-256, which has to match both signedData.SetDigestAlgorithm(pkcs7.OIDDigestAlgorithmSHA256) in the handler and the key’s own algorithm. A P-384 key wants EC_SIGN_P384_SHA384 and therefore Digest_Sha384 and the SHA-384 OID; changing one of the three without the others produces a signature over the wrong hash.

Like the AWS KMS handler, the signer must also implement EncryptionAlgorithmOID() asn1.ObjectIdentifier, which pkcs7 picks up through its EncryptionAlgorithmReporter interface. Without it AddSigner rejects the signer as an unknown private key type, because its fallback is a type switch over the standard library key types.

Limitations

Both the request and the response carry CRC32C checksums and the example checks them. A corrupt response from Sign returns an error, but the same check inside Public() calls log.Fatal, so an integrity failure while reading the public key takes the process down rather than surfacing an error. Worth changing if you lift the code.

InitSignature sizes the Contents entry by calling Sign with a real digest buffer, so every signature costs two AsymmetricSign calls: one to measure, one to sign. Passing a nil digest instead reserves sigLen zero bytes with no network round trip.

The signature is padded to sigLen, 8192 in the example, and copy silently truncates anything longer. The KMS client should be closed when you are done with it; the example defers gcKmsSign.client.Close().

The certificate in the example is self-signed with the KMS key, which validates nowhere. Replace it with a certificate issued for that key and set the full chain on the handler.

Run the example

GcKmsExternalSigner builds the client and signer, and getExternalSignatureAndSign creates the certificate and signs. The example takes four arguments, including the path to a service account credentials JSON file.

git clone https://github.com/unidoc/unipdf-examples.git
cd unipdf-examples/signatures
go run pdf_sign_external_google_cloud_kms.go input.pdf output.pdf credentials.json KEY_NAME

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