Google Cloud KMS (Key Management Service) provides enterprise-grade security for document signing by keeping the private key in a Hardware Security Module (HSM). The key never leaves Google’s infrastructure.
Why Use Google Cloud KMS?
Maximum Security : Private keys stored in FIPS 140-2 Level 3 certified HSMs
Compliance : Meets SOC 2, ISO 27001, PCI DSS, and other standards
Audit Trail : All signing operations logged in Google Cloud
Key Rotation : Automated key rotation without re-signing documents
Access Control : Fine-grained IAM permissions
Prerequisites
Google Cloud account with billing enabled
Cloud KMS API enabled
Service account with KMS permissions
A certificate for signing (can be self-signed or from a CA)
Setup Steps
1. Enable Cloud KMS API
# Enable the KMS API in your project
gcloud services enable cloudkms.googleapis.com
2. Create a Key Ring and Key
# Set your project ID
export PROJECT_ID = "your-project-id"
export LOCATION = "us-central1"
export KEYRING_NAME = "documenso-signing"
export KEY_NAME = "pdf-signing-key"
# Create a key ring
gcloud kms keyrings create $KEYRING_NAME \
--location $LOCATION \
--project $PROJECT_ID
# Create an asymmetric signing key (RSA 4096)
gcloud kms keys create $KEY_NAME \
--keyring $KEYRING_NAME \
--location $LOCATION \
--purpose "asymmetric-signing" \
--default-algorithm "rsa-sign-pkcs1-4096-sha256" \
--protection-level "hsm" \
--project $PROJECT_ID
The --protection-level "hsm" flag ensures the key is stored in a Hardware Security Module. This is required for compliance but costs more than software keys.
3. Get the Key Path
The key path format is:
projects/PROJECT_ID/locations/LOCATION/keyRings/KEYRING_NAME/cryptoKeys/KEY_NAME/cryptoKeyVersions/1
Example:
projects/my-project/locations/us-central1/keyRings/documenso-signing/cryptoKeys/pdf-signing-key/cryptoKeyVersions/1
You’ll need this for the NEXT_PRIVATE_SIGNING_GCLOUD_HSM_KEY_PATH environment variable.
4. Create a Service Account
export SERVICE_ACCOUNT_NAME = "documenso-signer"
# Create service account
gcloud iam service-accounts create $SERVICE_ACCOUNT_NAME \
--display-name "Documenso PDF Signer" \
--project $PROJECT_ID
# Grant KMS signing permission
gcloud kms keys add-iam-policy-binding $KEY_NAME \
--keyring $KEYRING_NAME \
--location $LOCATION \
--member "serviceAccount: $SERVICE_ACCOUNT_NAME @ $PROJECT_ID .iam.gserviceaccount.com" \
--role "roles/cloudkms.signerVerifier" \
--project $PROJECT_ID
# Create and download service account key
gcloud iam service-accounts keys create ~/documenso-kms-key.json \
--iam-account $SERVICE_ACCOUNT_NAME @ $PROJECT_ID .iam.gserviceaccount.com \
--project $PROJECT_ID
Store the documenso-kms-key.json file securely. Anyone with this file can sign documents using your KMS key.
5. Prepare the Certificate
You need a public certificate that corresponds to your KMS key. You can either:
Generate a self-signed certificate
Obtain a certificate from a Certificate Authority
Option A: Self-Signed Certificate
# Create a certificate signing request (CSR)
openssl req -new -key <( openssl rsa -in /dev/null -out /dev/stdout) \
-subj "/CN=Documenso/O=Your Organization" \
-out signing.csr
# Create a self-signed certificate (valid for 10 years)
openssl x509 -req -days 3650 -in signing.csr \
-signkey <( openssl rsa -in /dev/null -out /dev/stdout) \
-out signing.crt
Since the actual signing happens with the KMS key, the certificate is only used for embedding metadata in the PDF signature. The private key shown in OpenSSL commands above is not used - it’s just for generating the certificate structure.
Option B: Certificate from a CA
If you need signatures to appear as “trusted” in PDF viewers:
Generate a CSR
Submit it to a CA that’s in the Adobe Approved Trust List (AATL)
Receive your certificate chain
6. Prepare Certificate Files
You need to provide the certificate to Documenso in one of these formats:
Single certificate file (PEM format)
Certificate chain file (PEM format, includes intermediate certificates)
# If you have a chain, concatenate all certificates:
cat signing.crt intermediate.crt root.crt > chain.pem
Configuration
Documenso supports multiple ways to provide credentials and certificates:
Method 1: File Paths (VM/Server Deployment)
NEXT_PRIVATE_SIGNING_TRANSPORT = "gcloud-hsm"
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_KEY_PATH = "projects/my-project/locations/us-central1/keyRings/documenso-signing/cryptoKeys/pdf-signing-key/cryptoKeyVersions/1"
# Service account credentials
GOOGLE_APPLICATION_CREDENTIALS = "/app/credentials/documenso-kms-key.json"
# Certificate (choose ONE)
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_PUBLIC_CRT_FILE_PATH = "/app/certs/signing.crt"
# OR for chain:
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_CERT_CHAIN_FILE_PATH = "/app/certs/chain.pem"
Method 2: Base64-Encoded (Containers/Serverless)
# Convert credentials to base64
base64 -i documenso-kms-key.json | tr -d '\n' > credentials.base64.txt
base64 -i signing.crt | tr -d '\n' > cert.base64.txt
# OR for chain:
base64 -i chain.pem | tr -d '\n' > chain.base64.txt
Configure:
NEXT_PRIVATE_SIGNING_TRANSPORT = "gcloud-hsm"
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_KEY_PATH = "projects/my-project/locations/us-central1/keyRings/documenso-signing/cryptoKeys/pdf-signing-key/cryptoKeyVersions/1"
# Service account credentials (base64-encoded)
GOOGLE_APPLICATION_CREDENTIALS = "/tmp/documenso-creds.json"
NEXT_PRIVATE_SIGNING_GCLOUD_APPLICATION_CREDENTIALS_CONTENTS = "ewogICJ0eXBlIjogInNlcnZpY2VfYWNjb3VudCIs..."
# Certificate (choose ONE)
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_PUBLIC_CRT_FILE_CONTENTS = "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t..."
# OR for chain:
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_CERT_CHAIN_CONTENTS = "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t..."
When using NEXT_PRIVATE_SIGNING_GCLOUD_APPLICATION_CREDENTIALS_CONTENTS, the file specified in GOOGLE_APPLICATION_CREDENTIALS will be created automatically if it doesn’t exist.
Method 3: Google Secret Manager (Advanced)
Store the certificate in Google Secret Manager:
# Create secret
echo -n "$( cat chain.pem)" | gcloud secrets create documenso-signing-cert \
--data-file=- \
--project $PROJECT_ID
# Grant access to service account
gcloud secrets add-iam-policy-binding documenso-signing-cert \
--member= "serviceAccount: $SERVICE_ACCOUNT_NAME @ $PROJECT_ID .iam.gserviceaccount.com" \
--role= "roles/secretmanager.secretAccessor" \
--project $PROJECT_ID
Configure:
NEXT_PRIVATE_SIGNING_TRANSPORT = "gcloud-hsm"
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_KEY_PATH = "projects/my-project/locations/us-central1/keyRings/documenso-signing/cryptoKeys/pdf-signing-key/cryptoKeyVersions/1"
GOOGLE_APPLICATION_CREDENTIALS = "/app/credentials/documenso-kms-key.json"
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_SECRET_MANAGER_CERT_PATH = "projects/my-project/secrets/documenso-signing-cert/versions/latest"
Environment Variables Reference
NEXT_PRIVATE_SIGNING_TRANSPORT
Set to "gcloud-hsm" to use Google Cloud KMS signing. NEXT_PRIVATE_SIGNING_TRANSPORT = "gcloud-hsm"
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_KEY_PATH
Full path to the Google Cloud KMS key version. Format: projects/PROJECT_ID/locations/LOCATION/keyRings/KEYRING/cryptoKeys/KEY/cryptoKeyVersions/VERSION NEXT_PRIVATE_SIGNING_GCLOUD_HSM_KEY_PATH = "projects/my-project/locations/us-central1/keyRings/documenso-signing/cryptoKeys/pdf-signing-key/cryptoKeyVersions/1"
GOOGLE_APPLICATION_CREDENTIALS
Path to the service account JSON key file. GOOGLE_APPLICATION_CREDENTIALS = "/app/credentials/documenso-kms-key.json"
NEXT_PRIVATE_SIGNING_GCLOUD_APPLICATION_CREDENTIALS_CONTENTS
Base64-encoded contents of the service account JSON file. If provided, the file will be created at the path specified in GOOGLE_APPLICATION_CREDENTIALS. NEXT_PRIVATE_SIGNING_GCLOUD_APPLICATION_CREDENTIALS_CONTENTS = "ewogICJ0eXBlIjogInNlcnZpY2VfYWNjb3VudCIs..."
Certificate Configuration (Choose ONE method)
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_PUBLIC_CRT_FILE_PATH
Path to the public certificate file (PEM format). NEXT_PRIVATE_SIGNING_GCLOUD_HSM_PUBLIC_CRT_FILE_PATH = "/app/certs/signing.crt"
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_PUBLIC_CRT_FILE_CONTENTS
Base64-encoded contents of the public certificate file. NEXT_PRIVATE_SIGNING_GCLOUD_HSM_PUBLIC_CRT_FILE_CONTENTS = "LS0tLS1CRUdJTi..."
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_CERT_CHAIN_FILE_PATH
Path to the certificate chain file (PEM format). Use this instead of the single certificate if you have intermediate certificates. NEXT_PRIVATE_SIGNING_GCLOUD_HSM_CERT_CHAIN_FILE_PATH = "/app/certs/chain.pem"
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_CERT_CHAIN_CONTENTS
Base64-encoded contents of the certificate chain file. NEXT_PRIVATE_SIGNING_GCLOUD_HSM_CERT_CHAIN_CONTENTS = "LS0tLS1CRUdJTi..."
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_SECRET_MANAGER_CERT_PATH
Path to retrieve the certificate from Google Secret Manager. Format: projects/PROJECT_ID/secrets/SECRET_NAME/versions/VERSION NEXT_PRIVATE_SIGNING_GCLOUD_HSM_SECRET_MANAGER_CERT_PATH = "projects/my-project/secrets/documenso-signing-cert/versions/latest"
Complete Configuration Examples
Docker Compose
Kubernetes Secret
Vercel/Serverless
version : '3.8'
services :
documenso :
image : documenso/documenso:latest
environment :
NEXT_PRIVATE_SIGNING_TRANSPORT : gcloud-hsm
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_KEY_PATH : projects/my-project/locations/us-central1/keyRings/documenso-signing/cryptoKeys/pdf-signing-key/cryptoKeyVersions/1
GOOGLE_APPLICATION_CREDENTIALS : /app/credentials/documenso-kms-key.json
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_CERT_CHAIN_FILE_PATH : /app/certs/chain.pem
volumes :
- ./documenso-kms-key.json:/app/credentials/documenso-kms-key.json:ro
- ./chain.pem:/app/certs/chain.pem:ro
apiVersion : v1
kind : Secret
metadata :
name : documenso-signing
type : Opaque
stringData :
kms-credentials : |
{
"type": "service_account",
"project_id": "my-project",
...
}
certificate-chain : |
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
---
apiVersion : apps/v1
kind : Deployment
metadata :
name : documenso
spec :
template :
spec :
containers :
- name : documenso
env :
- name : NEXT_PRIVATE_SIGNING_TRANSPORT
value : "gcloud-hsm"
- name : NEXT_PRIVATE_SIGNING_GCLOUD_HSM_KEY_PATH
value : "projects/my-project/locations/us-central1/keyRings/documenso-signing/cryptoKeys/pdf-signing-key/cryptoKeyVersions/1"
- name : GOOGLE_APPLICATION_CREDENTIALS
value : "/app/credentials/kms-key.json"
- name : NEXT_PRIVATE_SIGNING_GCLOUD_HSM_CERT_CHAIN_FILE_PATH
value : "/app/certs/chain.pem"
volumeMounts :
- name : signing-credentials
mountPath : /app/credentials
readOnly : true
- name : signing-cert
mountPath : /app/certs
readOnly : true
volumes :
- name : signing-credentials
secret :
secretName : documenso-signing
items :
- key : kms-credentials
path : kms-key.json
- name : signing-cert
secret :
secretName : documenso-signing
items :
- key : certificate-chain
path : chain.pem
# Transport
NEXT_PRIVATE_SIGNING_TRANSPORT = "gcloud-hsm"
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_KEY_PATH = "projects/my-project/locations/us-central1/keyRings/documenso-signing/cryptoKeys/pdf-signing-key/cryptoKeyVersions/1"
# Credentials (base64-encoded JSON)
GOOGLE_APPLICATION_CREDENTIALS = "/tmp/documenso-creds.json"
NEXT_PRIVATE_SIGNING_GCLOUD_APPLICATION_CREDENTIALS_CONTENTS = "ewogICJ0eXBlIjogInNlcnZpY2VfYWNjb3VudCI..."
# Certificate chain (base64-encoded PEM)
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_CERT_CHAIN_CONTENTS = "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JS..."
# Optional: Timestamp authority
NEXT_PRIVATE_SIGNING_TIMESTAMP_AUTHORITY = "http://timestamp.digicert.com"
Cost Considerations
Google Cloud KMS pricing (as of 2024):
HSM key storage : ~$2.50/month per key version
Signing operations : ~$0.03 per 10,000 operations
Secret Manager (if used): ~$0.06 per 10,000 accesses
For most deployments, the cost is minimal. Signing 100,000 documents per month would cost approximately $3/month for KMS operations.
Troubleshooting
”No certificate found for Google Cloud HSM signing”
Cause: None of the certificate configuration methods are set.
Solution: Provide the certificate using one of these:
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_PUBLIC_CRT_FILE_PATH or NEXT_PRIVATE_SIGNING_GCLOUD_HSM_PUBLIC_CRT_FILE_CONTENTS
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_CERT_CHAIN_FILE_PATH or NEXT_PRIVATE_SIGNING_GCLOUD_HSM_CERT_CHAIN_CONTENTS
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_SECRET_MANAGER_CERT_PATH
”No key path provided for Google Cloud HSM signing”
Cause: NEXT_PRIVATE_SIGNING_GCLOUD_HSM_KEY_PATH is not set.
Solution: Set the full path to your KMS key version.
”Permission denied” errors
Cause: Service account doesn’t have the required permissions.
Solution: Ensure the service account has the roles/cloudkms.signerVerifier role:
gcloud kms keys add-iam-policy-binding $KEY_NAME \
--keyring $KEYRING_NAME \
--location $LOCATION \
--member "serviceAccount: $SERVICE_ACCOUNT_NAME @ $PROJECT_ID .iam.gserviceaccount.com" \
--role "roles/cloudkms.signerVerifier"
“Invalid certificate chain”
Cause: Certificate chain is in the wrong order or incomplete.
Solution: Ensure certificates are ordered correctly in the chain file:
# Correct order:
# 1. Your signing certificate
# 2. Intermediate CA certificate(s)
# 3. Root CA certificate
cat signing.crt intermediate.crt root.crt > chain.pem
Security Best Practices
Use HSM Protection Level
Always create keys with --protection-level "hsm" for compliance requirements.
Limit Service Account Permissions
Only grant the minimum required permissions (roles/cloudkms.signerVerifier).
Enable Audit Logging
Monitor all KMS operations in Cloud Logging: gcloud logging read "resource.type=cloudkms_cryptokey" --project $PROJECT_ID
Rotate Service Account Keys
Regularly rotate service account keys (recommended: every 90 days).
Use Secret Manager for Sensitive Data
Store certificates and credentials in Google Secret Manager instead of environment variables.
Next Steps
Environment Variables View all configuration options
Storage Setup Configure document storage