Skip to main content

Cloud KMS Setup for CODITECT License Signing

Overview

This document describes the Cloud KMS infrastructure deployed for signing CODITECT license tokens. Cloud KMS provides tamper-proof license signing using RSA-4096 asymmetric cryptography, enabling CODITECT's offline-first architecture where licenses can be verified locally without network access.

Deployment Date

Deployed: November 24, 2025 Status: ✅ Production Ready Test Results: All tests passed

Architecture

License Signing Flow

CODITECT License Server (FastAPI)

1. User requests license

2. Server creates license payload (tenant_id, user_id, expires_at, max_seats)

3. Server calls Cloud KMS asymmetric_sign()

4. Cloud KMS signs SHA-256 digest with private key (RSA-4096)

5. Server returns signed license to client

CODITECT Client (Local Machine)

6. Client retrieves public key from Cloud KMS (cached 1 hour)

7. Client verifies signature locally using cryptography library

8. Client runs if signature valid (no network required after initial fetch)

Security Benefits

  1. Tamper-Proof: Private key never leaves Cloud KMS (Hardware Security Module)
  2. Offline-Capable: Clients verify signatures locally with cached public key
  3. Non-Repudiation: Only authorized service account can sign licenses
  4. Cryptographically Secure: RSA-4096 with PKCS#1 v1.5 padding and SHA-256
  5. Audit Trail: All signing operations logged in Cloud Audit Logs

Cloud KMS Resources

Keyring

PropertyValue
Namecoditect-license-keys
Locationus-central1
Full Pathprojects/coditect-cloud-infra/locations/us-central1/keyRings/coditect-license-keys

Signing Key

PropertyValue
Key Namelicense-signing-key-v1
PurposeASYMMETRIC_SIGN
AlgorithmRSA_SIGN_PKCS1_4096_SHA256
Protection LevelSOFTWARE (upgrade to HSM for production)
Key Version1 (primary)
Full Pathprojects/coditect-cloud-infra/locations/us-central1/keyRings/coditect-license-keys/cryptoKeys/license-signing-key-v1
Key Version Pathprojects/coditect-cloud-infra/locations/us-central1/keyRings/coditect-license-keys/cryptoKeys/license-signing-key-v1/cryptoKeyVersions/1

Algorithm Details

  • Key Size: 4096-bit RSA
  • Signature Scheme: PKCS#1 v1.5 (RSA_SIGN_PKCS1)
  • Hash Function: SHA-256
  • Security Level: 128-bit (equivalent to AES-128)
  • NIST Recommendation: Approved for use until at least 2030

IAM Permissions

Service Account

PropertyValue
Service Accountcoditect-api-sa@coditect-cloud-infra.iam.gserviceaccount.com
Display NameCODITECT API Service Account
Creation DateNovember 24, 2025

Roles Granted

RolePurposePermission Details
roles/cloudkms.signerVerifierSign license tokenscloudkms.cryptoKeyVersions.useToSign
roles/cloudkms.viewerRetrieve public keycloudkms.cryptoKeys.get, cloudkms.cryptoKeyVersions.get, cloudkms.cryptoKeyVersions.viewPublicKey

IAM Policy

bindings:
- role: roles/cloudkms.signerVerifier
members:
- serviceAccount:coditect-api-sa@coditect-cloud-infra.iam.gserviceaccount.com

- role: roles/cloudkms.viewer
members:
- serviceAccount:coditect-api-sa@coditect-cloud-infra.iam.gserviceaccount.com

Python Integration

1. Sign License with Cloud KMS

from google.cloud import kms
import base64
import hashlib
import json


def sign_license_with_kms(license_data):
"""
Sign license data with Cloud KMS.

Args:
license_data (dict): License payload containing tenant_id, user_id,
expires_at, max_seats, etc.

Returns:
str: JSON string with 'data' and 'signature' fields
"""
client = kms.KeyManagementServiceClient()

# Full path to key version
key_name = (
'projects/coditect-cloud-infra/locations/us-central1/'
'keyRings/coditect-license-keys/cryptoKeys/license-signing-key-v1/'
'cryptoKeyVersions/1'
)

# Serialize license data (deterministic JSON)
message = json.dumps(license_data, sort_keys=True).encode('utf-8')

# Compute SHA-256 digest
digest = hashlib.sha256(message).digest()

# Sign with Cloud KMS
response = client.asymmetric_sign(
request={
'name': key_name,
'digest': {'sha256': digest}
}
)

# Base64-encode signature for JSON transport
signature = base64.b64encode(response.signature).decode('utf-8')

# Return signed license
return json.dumps({
'data': license_data,
'signature': signature
})


# Example usage
license_data = {
'tenant_id': 'coditect-tenant-123',
'user_id': 'user@example.com',
'expires_at': '2025-12-31T23:59:59Z',
'max_seats': 10,
'features': ['ai-agents', 'cloud-sync']
}

signed_license = sign_license_with_kms(license_data)
print(signed_license)

2. Retrieve Public Key from Cloud KMS

from google.cloud import kms


def get_public_key_from_kms():
"""
Retrieve RSA-4096 public key from Cloud KMS.

Returns:
str: PEM-encoded public key
"""
client = kms.KeyManagementServiceClient()

# Full path to key version
key_version_name = (
'projects/coditect-cloud-infra/locations/us-central1/'
'keyRings/coditect-license-keys/cryptoKeys/license-signing-key-v1/'
'cryptoKeyVersions/1'
)

# Retrieve public key
public_key = client.get_public_key(name=key_version_name)

return public_key.pem


# Example usage with Redis caching (1 hour TTL)
import redis

redis_client = redis.Redis(host='localhost', port=6379)

def get_public_key_cached():
"""Get public key with 1-hour Redis cache."""
cache_key = 'coditect:license:public_key'

# Check cache
cached_key = redis_client.get(cache_key)
if cached_key:
return cached_key.decode('utf-8')

# Fetch from Cloud KMS
public_key_pem = get_public_key_from_kms()

# Cache for 1 hour
redis_client.setex(cache_key, 3600, public_key_pem)

return public_key_pem

3. Verify Signature Locally (CODITECT Client)

from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
import base64
import json


def verify_license_signature(signed_license, public_key_pem):
"""
Verify license signature locally (offline-capable).

Args:
signed_license (str): JSON string with 'data' and 'signature' fields
public_key_pem (str): PEM-encoded RSA public key

Returns:
bool: True if signature is valid, False otherwise
"""
# Parse signed license
signed_data = json.loads(signed_license)
license_data = signed_data['data']
signature = base64.b64decode(signed_data['signature'])

# Recreate message (must match signing process exactly)
message = json.dumps(license_data, sort_keys=True).encode('utf-8')

# Load public key
public_key = serialization.load_pem_public_key(public_key_pem.encode('utf-8'))

# Verify signature
try:
public_key.verify(
signature,
message,
padding.PKCS1v15(),
hashes.SHA256()
)
return True
except Exception as e:
print(f"Signature verification failed: {e}")
return False


# Example usage in CODITECT client
signed_license = '{"data": {...}, "signature": "..."}'
public_key_pem = """-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoNtWBMluUPOMUHO7HrZq
...
-----END PUBLIC KEY-----"""

is_valid = verify_license_signature(signed_license, public_key_pem)
if is_valid:
print("✅ License is valid - CODITECT can run")
else:
print("❌ License is invalid or tampered - CODITECT will not run")

Security Considerations

Key Rotation Policy

Current: Manual rotation every 90 days Recommended for Production: Automated rotation every 30 days

Rotation Process:

  1. Create new key version: gcloud kms keys versions create license-signing-key-v1 --location=us-central1 --keyring=coditect-license-keys
  2. Update application to sign with new version
  3. Keep old version active for 30 days (grace period for clients to update cached public key)
  4. Disable old version: gcloud kms keys versions disable 1 --location=us-central1 --keyring=coditect-license-keys --key=license-signing-key-v1

Production Hardening Recommendations

  1. Upgrade to HSM Protection:

    gcloud kms keys create license-signing-key-v2 \
    --location=us-central1 \
    --keyring=coditect-license-keys \
    --purpose=asymmetric-signing \
    --default-algorithm=rsa-sign-pkcs1-4096-sha256 \
    --protection-level=HSM
  2. Enable Cloud KMS Audit Logging:

    • Already enabled by default
    • Review logs regularly: gcloud logging read "resource.type=cloudkms_cryptokey"
  3. Implement Rate Limiting:

    • Cloud KMS quota: 60,000 requests/minute
    • Implement application-level rate limiting to prevent abuse
  4. Monitor Key Usage:

    • Set up alerts for unusual signing activity
    • Track signing requests per minute/hour/day
  5. Implement Key Version Tracking:

    • Store key version in signed license
    • Support multiple active key versions simultaneously
    • Graceful migration to new keys

Public Key Caching Strategy

Recommended TTL: 1 hour (3600 seconds)

Rationale:

  • Reduces Cloud KMS API calls (cost optimization)
  • Minimal delay during key rotation (max 1 hour)
  • Balances performance and security

Implementation:

# Redis cache key format
CACHE_KEY = f"coditect:license:public_key:v{key_version}"
TTL = 3600 # 1 hour

# Store with key version for rotation support
redis_client.setex(CACHE_KEY, TTL, public_key_pem)

Access Control Best Practices

  1. Principle of Least Privilege:

    • Only coditect-api-sa can sign licenses
    • No other service accounts should have cloudkms.signerVerifier role
  2. Separate Service Accounts:

    • Use different service accounts for dev/staging/prod
    • Example: coditect-api-sa-dev, coditect-api-sa-prod
  3. Audit IAM Changes:

    • Monitor IAM policy changes on Cloud KMS keys
    • Alert on unauthorized permission grants
  4. VPC Service Controls (Future):

    • Consider VPC Service Controls for additional perimeter security
    • Restrict Cloud KMS access to specific VPC networks

Cost Analysis

Cloud KMS Pricing (as of November 2025)

OperationPriceExpected UsageMonthly Cost
Key Versions (Active)$0.06/key/month2 keys (current + rotation)$0.12
Asymmetric Signing Operations$0.03 per 10,000 operations100,000 signs/month$0.30
Public Key RetrievalsFreeUnlimited (cached)$0.00
Total$0.42/month

Production Estimate (10x scale):

  • Key Versions: $0.12/month (2 active keys)
  • Signing Operations: $3.00/month (1M signs)
  • Total: $3.12/month

Cost Optimization:

  • Cache public keys (1 hour TTL) - eliminates retrieval costs
  • Batch signing operations if possible
  • Monitor usage with Cloud Monitoring

Testing

Test Script

A comprehensive test script is available at /test-kms-signing.py.

Run test:

# Create virtual environment
python3 -m venv venv
source venv/bin/activate

# Install dependencies
pip install google-cloud-kms cryptography

# Authenticate with correct project
gcloud auth application-default login --project=coditect-cloud-infra

# Run test
python test-kms-signing.py

Expected output:

======================================================================
CODITECT Cloud KMS Signing Test
======================================================================

1. Signing license with Cloud KMS...
✅ Signed license (first 100 chars): {"data": {"tenant_id": "test-tenant-123", ...

2. Retrieving public key from Cloud KMS...
✅ Public key retrieved (first 100 chars): -----BEGIN PUBLIC KEY-----...

3. Verifying signature locally...
✅ Signature valid: True

======================================================================
✅ SUCCESS: Cloud KMS signing and verification workflow complete!
======================================================================

Test Results (November 24, 2025)

  • ✅ Cloud KMS keyring created
  • ✅ RSA-4096 signing key created
  • ✅ IAM permissions granted to service account
  • ✅ Signing operation successful
  • ✅ Public key retrieval successful
  • ✅ Local signature verification successful
  • ✅ End-to-end workflow validated

Monitoring and Alerting

Key Metrics to Monitor

  1. Signing Request Rate:

    • Metric: cloudkms.googleapis.com/request_count (filter: method=AsymmetricSign)
    • Alert: >1000 requests/minute (potential abuse)
  2. Signing Latency:

    • Metric: cloudkms.googleapis.com/request_latencies (filter: method=AsymmetricSign)
    • Alert: p99 >500ms (performance degradation)
  3. Error Rate:

    • Metric: cloudkms.googleapis.com/request_count (filter: response_code!=200)
    • Alert: >1% error rate
  4. IAM Policy Changes:

    • Log: protoPayload.methodName="SetIamPolicy"
    • Alert: Any unauthorized IAM policy change

Grafana Dashboard (Future)

Planned metrics:

  • Signing operations per minute
  • Signing latency (p50, p95, p99)
  • Public key cache hit rate
  • Active key versions
  • IAM policy audit trail

Troubleshooting

Common Issues

Issue: 403 Permission Denied when signing

Solution:

  1. Verify service account has roles/cloudkms.signerVerifier role
  2. Check IAM policy: gcloud kms keys get-iam-policy license-signing-key-v1 --location=us-central1 --keyring=coditect-license-keys
  3. Ensure application is using correct service account credentials

Issue: 404 Key Not Found

Solution:

  1. Verify key exists: gcloud kms keys list --location=us-central1 --keyring=coditect-license-keys
  2. Check key name spelling in application code
  3. Ensure correct GCP project is configured

Issue: Signature verification fails

Solution:

  1. Ensure message serialization is identical (use json.dumps(data, sort_keys=True))
  2. Verify public key is from correct key version
  3. Check signature encoding (should be base64)
  4. Ensure cryptography library uses same hash (SHA-256) and padding (PKCS1v15)

Issue: High Cloud KMS costs

Solution:

  1. Implement public key caching (1 hour TTL)
  2. Review signing request rate (should match license acquisition rate)
  3. Check for unnecessary signing operations (e.g., re-signing same license)

Deployment Commands Reference

# Enable Cloud KMS API
gcloud services enable cloudkms.googleapis.com --project=coditect-cloud-infra

# Create keyring
gcloud kms keyrings create coditect-license-keys --location=us-central1

# Create signing key
gcloud kms keys create license-signing-key-v1 \
--location=us-central1 \
--keyring=coditect-license-keys \
--purpose=asymmetric-signing \
--default-algorithm=rsa-sign-pkcs1-4096-sha256

# Create service account
gcloud iam service-accounts create coditect-api-sa \
--display-name="CODITECT API Service Account" \
--project=coditect-cloud-infra

# Grant signerVerifier role
gcloud kms keys add-iam-policy-binding license-signing-key-v1 \
--location=us-central1 \
--keyring=coditect-license-keys \
--member="serviceAccount:coditect-api-sa@coditect-cloud-infra.iam.gserviceaccount.com" \
--role="roles/cloudkms.signerVerifier"

# Grant viewer role
gcloud kms keys add-iam-policy-binding license-signing-key-v1 \
--location=us-central1 \
--keyring=coditect-license-keys \
--member="serviceAccount:coditect-api-sa@coditect-cloud-infra.iam.gserviceaccount.com" \
--role="roles/cloudkms.viewer"

# List keys
gcloud kms keys list --location=us-central1 --keyring=coditect-license-keys

# Get public key
gcloud kms keys versions get-public-key 1 \
--location=us-central1 \
--keyring=coditect-license-keys \
--key=license-signing-key-v1 \
--output-file=public-key.pem

Contact


Document Version: 1.0 Last Updated: November 24, 2025 Status: ✅ Production Ready