Production key management and certificate authorities
Audience: Infrastructure Engineers, System Administrators Prerequisites: Completed Quick Start Guide Time: ~30 minutes
By the end of this tutorial, you will:
- Understand key management best practices
- Generate production-grade signing keys
- Manage keys across multiple environments
- Work with certificate authorities (CAs)
- Implement key rotation strategies
- Secure keys in production systems
| Aspect | Development | Production |
|---|---|---|
| Key Type | RSA 2048-bit | RSA 4096-bit or ECDSA P-384 |
| Certificate | Self-signed | CA-signed |
| Storage | Developer machine | Secure key store |
| Permissions | 0600 (user only) | 0400 (read-only) |
| Lifetime | 1 year | 2-5 years |
| Rotation | Manual | Automated |
| Backup | Optional | Required |
pytest-jux supports two cryptographic algorithms:
jux-keygen --output prod_key.pem \
--type rsa \
--bits 4096 \
--cert \
--subject "CN=pytest-jux-prod,O=YourOrg,C=US" \
--days-valid 730Pros:
- ✅ Widely supported
- ✅ Well-understood security properties
- ✅ Compatible with older systems
Cons:
- ❌ Larger signatures (~512 bytes)
- ❌ Slower signing (~50ms per report)
Use for: General-purpose production deployments
jux-keygen --output prod_key.pem \
--type ecdsa \
--curve P-384 \
--cert \
--subject "CN=pytest-jux-prod,O=YourOrg,C=US" \
--days-valid 730Pros:
- ✅ Smaller signatures (~128 bytes)
- ✅ Faster signing (~20ms per report)
- ✅ Modern cryptography
Cons:
- ❌ Less widely supported
- ❌ May not work with older verification tools
Use for: High-volume testing, performance-critical pipelines
# Create key directory
mkdir -p ~/.jux/production
chmod 700 ~/.jux/production
# Generate production key with certificate
jux-keygen \
--output ~/.jux/production/signing_key.pem \
--type rsa \
--bits 4096 \
--cert \
--subject "CN=pytest-jux-prod,O=YourCompany,OU=Engineering,C=US" \
--days-valid 730
# Verify permissions
ls -la ~/.jux/production/
# -rw------- 1 user staff 3272 Oct 17 14:30 signing_key.pem
# -rw-r--r-- 1 user staff 1234 Oct 17 14:30 signing_key.crtGenerate separate keys for each environment:
# Development
jux-keygen --output ~/.jux/dev/signing_key.pem \
--type rsa --bits 2048 --cert \
--subject "CN=pytest-jux-dev,O=YourCompany,OU=Engineering" \
--days-valid 365
# Staging
jux-keygen --output ~/.jux/staging/signing_key.pem \
--type rsa --bits 4096 --cert \
--subject "CN=pytest-jux-staging,O=YourCompany,OU=Engineering" \
--days-valid 730
# Production
jux-keygen --output ~/.jux/production/signing_key.pem \
--type rsa --bits 4096 --cert \
--subject "CN=pytest-jux-prod,O=YourCompany,OU=Engineering" \
--days-valid 730Why separate keys?
- Limits blast radius if one key is compromised
- Enables environment-specific trust policies
- Simplifies key rotation per environment
Self-signed certificates work for testing, but production should use CA-signed certificates.
If your organization has an internal CA:
# 1. Generate Certificate Signing Request (CSR)
openssl req -new -key ~/.jux/production/signing_key.pem \
-out pytest-jux-prod.csr \
-subj "/CN=pytest-jux-prod/O=YourCompany/OU=Engineering/C=US"
# 2. Submit CSR to your CA
# (Process varies by organization)
# 3. Receive signed certificate
# Save as ~/.jux/production/signing_key.crt
# 4. Verify certificate chain
openssl verify -CAfile /path/to/ca-bundle.crt \
~/.jux/production/signing_key.crtFor public infrastructure (less common for test signing):
# Use Let's Encrypt, DigiCert, etc.
# Follow CA-specific enrollment process
# Note: pytest-jux signatures are typically for internal use,
# so public CAs are rarely necessary# Already generated by jux-keygen --cert
# Fine for development, NOT for productionVerifiers need the public certificate (NOT the private key).
# Certificate is already in signing_key.crt
cp ~/.jux/production/signing_key.crt /path/to/distribution/
# Or extract from private key if needed
openssl rsa -in ~/.jux/production/signing_key.pem \
-pubout -out public_key.pem# Copy to verification systems
scp ~/.jux/production/signing_key.crt \
verifier-host:/etc/jux/trusted-certs/
# Or publish to shared location
aws s3 cp ~/.jux/production/signing_key.crt \
s3://company-certs/jux/prod/signing_key.crtImportant: Never distribute the private key (.pem file)!
# Private key: owner read/write only
chmod 600 ~/.jux/production/signing_key.pem
# Certificate: world-readable (public key)
chmod 644 ~/.jux/production/signing_key.crt
# Directory: owner access only
chmod 700 ~/.jux/productionGitHub Actions:
# Store private key as secret
# Settings → Secrets → JUX_SIGNING_KEY
- name: Run tests with signing
run: |
echo "${{ secrets.JUX_SIGNING_KEY }}" > /tmp/signing_key.pem
chmod 600 /tmp/signing_key.pem
pytest --junit-xml=report.xml
env:
JUX_KEY_PATH: /tmp/signing_key.pemGitLab CI:
# Settings → CI/CD → Variables → JUX_SIGNING_KEY
test:
script:
- echo "$JUX_SIGNING_KEY" > /tmp/signing_key.pem
- chmod 600 /tmp/signing_key.pem
- pytest --junit-xml=report.xml
variables:
JUX_KEY_PATH: /tmp/signing_key.pemJenkins:
// Use Credentials Plugin
withCredentials([file(credentialsId: 'jux-signing-key', variable: 'KEY_FILE')]) {
sh '''
chmod 600 $KEY_FILE
pytest --junit-xml=report.xml
'''
}Note: pytest-jux does not currently support HSM integration. This is a future consideration (see CLAUDE.md).
For now, use file-based keys with strict access controls.
Regular key rotation limits exposure from compromised keys.
| Environment | Rotation Frequency | Overlap Period |
|---|---|---|
| Development | Annually | 30 days |
| Staging | Every 2 years | 60 days |
| Production | Every 2-3 years | 90 days |
# 1. Generate new key (keep old key active)
jux-keygen --output ~/.jux/production/signing_key_v2.pem \
--type rsa --bits 4096 --cert \
--subject "CN=pytest-jux-prod-v2,O=YourCompany" \
--days-valid 730
# 2. Deploy new key to signing systems
ansible-playbook deploy-signing-key.yml \
--extra-vars "key_version=v2"
# 3. Overlap period (both keys valid)
# Old key: ~/.jux/production/signing_key.pem (sign)
# New key: ~/.jux/production/signing_key_v2.pem (ready)
# 4. Switch to new key
mv ~/.jux/production/signing_key.pem \
~/.jux/production/signing_key_v1_archived.pem
mv ~/.jux/production/signing_key_v2.pem \
~/.jux/production/signing_key.pem
# 5. Update verification systems with new certificate
# Deploy signing_key.crt to all verifiers
# 6. After overlap period, decommission old key
# Keep old certificate for historical verification
mv ~/.jux/production/signing_key_v1_archived.pem \
/secure/archive/signing_key_v1_$(date +%Y%m%d).pemUsing Let's Encrypt-style automation:
#!/bin/bash
# rotate-jux-key.sh
CURRENT_KEY=~/.jux/production/signing_key.pem
NEW_KEY=~/.jux/production/signing_key_new.pem
# Generate new key
jux-keygen --output $NEW_KEY --type rsa --bits 4096 --cert \
--subject "CN=pytest-jux-prod,O=YourCompany" \
--days-valid 730
# Test new key
pytest --junit-xml=/tmp/test.xml
jux-sign /tmp/test.xml --key $NEW_KEY --output /tmp/test-signed.xml
jux-verify /tmp/test-signed.xml --cert ${NEW_KEY%.pem}.crt
# If test passes, rotate
if [ $? -eq 0 ]; then
mv $CURRENT_KEY ${CURRENT_KEY%.pem}_$(date +%Y%m%d).pem
mv $NEW_KEY $CURRENT_KEY
echo "Key rotated successfully"
else
echo "Key rotation failed"
exit 1
fi# Create encrypted backup
tar czf jux-keys-backup-$(date +%Y%m%d).tar.gz ~/.jux/
gpg --symmetric --cipher-algo AES256 jux-keys-backup-*.tar.gz
# Store in secure location
aws s3 cp jux-keys-backup-*.tar.gz.gpg \
s3://company-backups/jux-keys/ \
--storage-class GLACIER
# Clean up local backup
rm jux-keys-backup-*.tar.gz jux-keys-backup-*.tar.gz.gpg# Retrieve backup
aws s3 cp s3://company-backups/jux-keys/jux-keys-backup-20251017.tar.gz.gpg .
# Decrypt
gpg --decrypt jux-keys-backup-20251017.tar.gz.gpg > jux-keys-backup.tar.gz
# Extract
tar xzf jux-keys-backup.tar.gz
# Restore keys
cp -r .jux/ ~/.jux/
chmod 700 ~/.jux/
chmod 600 ~/.jux/**/*.pemDevelopment:
# ~/.config/jux/config
[jux]
key_path = ~/.jux/dev/signing_key.pem
cert_path = ~/.jux/dev/signing_key.crtStaging:
# Environment variables (CI/CD)
export JUX_KEY_PATH=/etc/jux/staging/signing_key.pem
export JUX_CERT_PATH=/etc/jux/staging/signing_key.crtProduction:
# Environment variables (CI/CD)
export JUX_KEY_PATH=/etc/jux/production/signing_key.pem
export JUX_CERT_PATH=/etc/jux/production/signing_key.crt# deploy-signing-key.yml
---
- name: Deploy pytest-jux signing keys
hosts: ci_runners
vars:
key_source: "{{ lookup('env', 'JUX_KEY_VAULT_PATH') }}"
key_dest: /etc/jux/{{ environment }}/signing_key.pem
cert_dest: /etc/jux/{{ environment }}/signing_key.crt
tasks:
- name: Create key directory
file:
path: /etc/jux/{{ environment }}
state: directory
mode: '0700'
owner: jenkins
group: jenkins
- name: Copy private key
copy:
src: "{{ key_source }}/signing_key.pem"
dest: "{{ key_dest }}"
mode: '0600'
owner: jenkins
group: jenkins
- name: Copy certificate
copy:
src: "{{ key_source }}/signing_key.crt"
dest: "{{ cert_dest }}"
mode: '0644'
owner: jenkins
group: jenkins- Use strong keys: RSA 4096-bit or ECDSA P-384 minimum for production
- Restrict permissions: 0600 for private keys, 0700 for directories
- Rotate regularly: Every 2-3 years for production keys
- Backup encrypted: Use GPG or similar for backups
- Separate environments: Different keys for dev/staging/prod
- Audit access: Log who accesses private keys
- Use CI/CD secrets: Never commit keys to git
- Commit to git: Never check in private keys
- Share keys: Each environment should have separate keys
- Use weak keys: RSA 1024 is too weak
- Skip backups: Lost keys = can't verify old reports
- Use self-signed in prod: Get CA-signed certificates
- Ignore permissions: World-readable private keys are compromised
- Forget rotation: Old keys increase risk over time
Problem:
ERROR: Key file not found: ~/.jux/signing_key.pem
Solution:
# Verify key exists
ls -la ~/.jux/signing_key.pem
# Check JUX_KEY_PATH environment variable
echo $JUX_KEY_PATH
# Verify configuration
jux-config dump | grep key_pathProblem:
ERROR: Invalid private key format
Solution:
# Verify key is PEM format
head -1 ~/.jux/signing_key.pem
# Should show: -----BEGIN PRIVATE KEY----- or -----BEGIN RSA PRIVATE KEY-----
# Convert if needed (rarely necessary)
openssl rsa -in old_key.pem -out new_key.pemProblem:
ERROR: Permission denied reading key file
Solution:
# Fix permissions
chmod 600 ~/.jux/signing_key.pem
chown $USER ~/.jux/signing_key.pem
# Verify
ls -la ~/.jux/signing_key.pemProblem:
ERROR: Certificate does not match key
Solution:
# Verify certificate matches key
openssl x509 -in signing_key.crt -noout -modulus > cert_modulus
openssl rsa -in signing_key.pem -noout -modulus > key_modulus
diff cert_modulus key_modulus
# If different, regenerate certificate
jux-keygen --output signing_key.pem --cert --forceNow that you have production keys set up:
- CI/CD Deployment - Integrate signing into your pipeline
- Multi-Environment Configuration - Configure per-environment keys
- Troubleshooting Guide - Fix common issues
- Quick Start - Basic key generation
- Choosing Storage Modes - Select storage strategy
- Security Policy - Security best practices
- Cryptographic Standards - Algorithm details
Use this checklist for production key deployment:
- Generated production-grade keys (RSA 4096 or ECDSA P-384)
- Set secure file permissions (0600 for keys, 0700 for directories)
- Obtained CA-signed certificates (not self-signed)
- Distributed public certificates to verifiers
- Configured CI/CD secrets management
- Implemented key rotation schedule
- Created encrypted backups
- Documented key locations and procedures
- Tested key rotation process
- Audited key access controls
- Separated keys per environment
- Verified keys not committed to git