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
- Tamper-Proof: Private key never leaves Cloud KMS (Hardware Security Module)
- Offline-Capable: Clients verify signatures locally with cached public key
- Non-Repudiation: Only authorized service account can sign licenses
- Cryptographically Secure: RSA-4096 with PKCS#1 v1.5 padding and SHA-256
- Audit Trail: All signing operations logged in Cloud Audit Logs
Cloud KMS Resources
Keyring
| Property | Value |
|---|---|
| Name | coditect-license-keys |
| Location | us-central1 |
| Full Path | projects/coditect-cloud-infra/locations/us-central1/keyRings/coditect-license-keys |
Signing Key
| Property | Value |
|---|---|
| Key Name | license-signing-key-v1 |
| Purpose | ASYMMETRIC_SIGN |
| Algorithm | RSA_SIGN_PKCS1_4096_SHA256 |
| Protection Level | SOFTWARE (upgrade to HSM for production) |
| Key Version | 1 (primary) |
| Full Path | projects/coditect-cloud-infra/locations/us-central1/keyRings/coditect-license-keys/cryptoKeys/license-signing-key-v1 |
| Key Version Path | projects/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
| Property | Value |
|---|---|
| Service Account | coditect-api-sa@coditect-cloud-infra.iam.gserviceaccount.com |
| Display Name | CODITECT API Service Account |
| Creation Date | November 24, 2025 |
Roles Granted
| Role | Purpose | Permission Details |
|---|---|---|
roles/cloudkms.signerVerifier | Sign license tokens | cloudkms.cryptoKeyVersions.useToSign |
roles/cloudkms.viewer | Retrieve public key | cloudkms.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:
- Create new key version:
gcloud kms keys versions create license-signing-key-v1 --location=us-central1 --keyring=coditect-license-keys - Update application to sign with new version
- Keep old version active for 30 days (grace period for clients to update cached public key)
- Disable old version:
gcloud kms keys versions disable 1 --location=us-central1 --keyring=coditect-license-keys --key=license-signing-key-v1
Production Hardening Recommendations
-
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 -
Enable Cloud KMS Audit Logging:
- Already enabled by default
- Review logs regularly:
gcloud logging read "resource.type=cloudkms_cryptokey"
-
Implement Rate Limiting:
- Cloud KMS quota: 60,000 requests/minute
- Implement application-level rate limiting to prevent abuse
-
Monitor Key Usage:
- Set up alerts for unusual signing activity
- Track signing requests per minute/hour/day
-
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
-
Principle of Least Privilege:
- Only
coditect-api-sacan sign licenses - No other service accounts should have
cloudkms.signerVerifierrole
- Only
-
Separate Service Accounts:
- Use different service accounts for dev/staging/prod
- Example:
coditect-api-sa-dev,coditect-api-sa-prod
-
Audit IAM Changes:
- Monitor IAM policy changes on Cloud KMS keys
- Alert on unauthorized permission grants
-
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)
| Operation | Price | Expected Usage | Monthly Cost |
|---|---|---|---|
| Key Versions (Active) | $0.06/key/month | 2 keys (current + rotation) | $0.12 |
| Asymmetric Signing Operations | $0.03 per 10,000 operations | 100,000 signs/month | $0.30 |
| Public Key Retrievals | Free | Unlimited (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
-
Signing Request Rate:
- Metric:
cloudkms.googleapis.com/request_count(filter: method=AsymmetricSign) - Alert: >1000 requests/minute (potential abuse)
- Metric:
-
Signing Latency:
- Metric:
cloudkms.googleapis.com/request_latencies(filter: method=AsymmetricSign) - Alert: p99 >500ms (performance degradation)
- Metric:
-
Error Rate:
- Metric:
cloudkms.googleapis.com/request_count(filter: response_code!=200) - Alert: >1% error rate
- Metric:
-
IAM Policy Changes:
- Log:
protoPayload.methodName="SetIamPolicy" - Alert: Any unauthorized IAM policy change
- Log:
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:
- Verify service account has
roles/cloudkms.signerVerifierrole - Check IAM policy:
gcloud kms keys get-iam-policy license-signing-key-v1 --location=us-central1 --keyring=coditect-license-keys - Ensure application is using correct service account credentials
Issue: 404 Key Not Found
Solution:
- Verify key exists:
gcloud kms keys list --location=us-central1 --keyring=coditect-license-keys - Check key name spelling in application code
- Ensure correct GCP project is configured
Issue: Signature verification fails
Solution:
- Ensure message serialization is identical (use
json.dumps(data, sort_keys=True)) - Verify public key is from correct key version
- Check signature encoding (should be base64)
- Ensure cryptography library uses same hash (SHA-256) and padding (PKCS1v15)
Issue: High Cloud KMS costs
Solution:
- Implement public key caching (1 hour TTL)
- Review signing request rate (should match license acquisition rate)
- Check for unnecessary signing operations (e.g., re-signing same license)
Related Documentation
- docs/workflows/W01-LICENSE-ACQUISITION.md - Complete license acquisition flow
- docs/architecture/c2-container-diagram.md - System architecture with Cloud KMS
- docs/guides/SECURITY-BEST-PRACTICES.md - Security guidelines
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
- Owner: AZ1.AI INC
- Lead: Hal Casteel, Founder/CEO/CTO
- Repository: https://github.com/coditect-ai/coditect-cloud-infra
Document Version: 1.0 Last Updated: November 24, 2025 Status: ✅ Production Ready