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/123Wiring 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_NAMEIf this is your first time using UniPDF, follow the getting started guide to create an API key and set up your development environment.