X.509 Certificates¶
Learn how to generate and manage X.509 certificates for secure communication.
Overview¶
X.509 certificates are digital documents that bind a public key to an identity. Foundation provides utilities for creating self-signed certificates, certificate signing requests (CSRs), and managing certificate chains.
Common use cases: - Development TLS/SSL certificates - Internal service authentication - Client certificates for mutual TLS - Code signing certificates
Prerequisites¶
Install crypto extras:
Generate Self-Signed Certificate¶
Create a self-signed certificate for development or testing:
from provide.foundation.crypto.certificates import generate_self_signed_cert
from pathlib import Path
# Generate certificate
cert_pem, private_key_pem = generate_self_signed_cert(
common_name="example.com",
organization="My Company",
validity_days=365
)
# Save to files
Path("cert.pem").write_text(cert_pem)
Path("key.pem").write_text(private_key_pem)
Output:
- cert.pem: Public certificate in PEM format
- key.pem: Private key in PEM format
Certificate with Subject Alternative Names (SAN)¶
Create certificates valid for multiple domains:
from provide.foundation.crypto.certificates import generate_self_signed_cert
# Certificate valid for multiple domains
cert_pem, key_pem = generate_self_signed_cert(
common_name="api.example.com",
organization="Example Corp",
subject_alt_names=[
"api.example.com",
"www.api.example.com",
"*.api.example.com", # Wildcard
"192.168.1.100", # IP address
],
validity_days=365
)
When to use SAN: - Multiple subdomains on same certificate - Load balancers with multiple backends - Development environments with various hostnames - Microservices with service discovery
Certificate Configuration¶
Organizational Details¶
Provide complete organizational information:
cert_pem, key_pem = generate_self_signed_cert(
common_name="services.mycompany.com",
organization="My Company Inc",
organizational_unit="Engineering",
country="US",
state="California",
locality="San Francisco",
email="[email protected]",
validity_days=730, # 2 years
)
Key Size and Algorithm¶
Specify cryptographic parameters:
from provide.foundation.crypto.certificates import generate_self_signed_cert
# RSA 4096-bit key
cert_pem, key_pem = generate_self_signed_cert(
common_name="secure.example.com",
key_size=4096, # Default is 2048
algorithm="RSA",
validity_days=365,
)
# ED25519 (faster, smaller keys)
cert_pem, key_pem = generate_self_signed_cert(
common_name="fast.example.com",
algorithm="ED25519",
validity_days=365,
)
Algorithm comparison:
| Algorithm | Key Size | Speed | Security | Use Case |
|---|---|---|---|---|
| RSA-2048 | 2048 bits | Medium | High | Standard web servers |
| RSA-4096 | 4096 bits | Slow | Very High | High-security applications |
| ED25519 | 256 bits | Very Fast | High | Modern applications, IoT |
Certificate Signing Request (CSR)¶
Generate a CSR for submission to a Certificate Authority:
from provide.foundation.crypto.certificates import generate_csr
# Generate private key and CSR
csr_pem, private_key_pem = generate_csr(
common_name="www.example.com",
organization="Example Inc",
country="US",
state="California",
locality="San Francisco",
email="[email protected]",
)
# Save CSR for submission to CA
Path("request.csr").write_text(csr_pem)
Path("private.key").write_text(private_key_pem)
Next steps with CSR: 1. Submit CSR to Certificate Authority (Let's Encrypt, DigiCert, etc.) 2. Complete domain validation 3. Receive signed certificate from CA 4. Use signed certificate with your private key
Certificate Chain¶
Work with certificate chains (certificate + intermediate + root):
from provide.foundation.crypto.certificates import load_certificate_chain
# Load certificate chain
chain = load_certificate_chain("fullchain.pem")
print(f"Chain contains {len(chain)} certificates")
for i, cert in enumerate(chain):
print(f"Certificate {i}: {cert.subject}")
print(f" Issuer: {cert.issuer}")
print(f" Valid until: {cert.not_valid_after}")
Create Certificate Chain¶
Combine certificates into a chain:
from pathlib import Path
# Read individual certificates
server_cert = Path("server.crt").read_text()
intermediate_cert = Path("intermediate.crt").read_text()
root_cert = Path("root.crt").read_text()
# Create full chain
full_chain = server_cert + intermediate_cert + root_cert
# Save full chain
Path("fullchain.pem").write_text(full_chain)
Certificate Verification¶
Verify certificate validity and properties:
from provide.foundation.crypto.certificates import verify_certificate
from datetime import datetime
# Load and verify certificate
cert = load_certificate("cert.pem")
# Check expiration
if cert.not_valid_after < datetime.now():
print("⚠️ Certificate has expired!")
else:
days_remaining = (cert.not_valid_after - datetime.now()).days
print(f"✅ Certificate valid for {days_remaining} more days")
# Verify hostname
if verify_hostname(cert, "example.com"):
print("✅ Certificate valid for example.com")
else:
print("❌ Certificate not valid for this hostname")
Extract Certificate Information¶
Get certificate details programmatically:
from provide.foundation.crypto.certificates import get_certificate_info
# Load certificate
info = get_certificate_info("cert.pem")
print(f"Subject: {info['subject']}")
print(f"Issuer: {info['issuer']}")
print(f"Valid from: {info['not_before']}")
print(f"Valid until: {info['not_after']}")
print(f"Serial number: {info['serial_number']}")
print(f"Key algorithm: {info['key_algorithm']}")
print(f"Key size: {info['key_size']} bits")
print(f"SAN: {info['subject_alt_names']}")
Common Patterns¶
Development TLS Server¶
Create certificates for local HTTPS development:
from provide.foundation.crypto.certificates import generate_self_signed_cert
from pathlib import Path
def setup_dev_tls():
"""Set up TLS certificates for local development."""
cert_dir = Path("certs")
cert_dir.mkdir(exist_ok=True)
# Generate cert for localhost
cert_pem, key_pem = generate_self_signed_cert(
common_name="localhost",
organization="Development",
subject_alt_names=[
"localhost",
"127.0.0.1",
"::1",
"*.localhost", # For subdomains
],
validity_days=365,
)
# Save certificates
cert_file = cert_dir / "localhost.crt"
key_file = cert_dir / "localhost.key"
cert_file.write_text(cert_pem)
key_file.write_text(key_pem)
print(f"✅ Development certificates created:")
print(f" Certificate: {cert_file}")
print(f" Private key: {key_file}")
return cert_file, key_file
# Use with web server
cert_file, key_file = setup_dev_tls()
# Example with uvicorn (FastAPI)
# uvicorn main:app --ssl-keyfile=certs/localhost.key --ssl-certfile=certs/localhost.crt
Client Certificate Authentication¶
Generate client certificates for mutual TLS:
from provide.foundation.crypto.certificates import generate_client_cert
def create_client_cert(client_name):
"""Create client certificate for mutual TLS."""
cert_pem, key_pem = generate_client_cert(
common_name=client_name,
organization="Client Services",
email=f"{client_name}@example.com",
validity_days=365,
)
# Save client credentials
Path(f"{client_name}.crt").write_text(cert_pem)
Path(f"{client_name}.key").write_text(key_pem)
return cert_pem, key_pem
# Create certificates for different clients
create_client_cert("service-a")
create_client_cert("service-b")
create_client_cert("mobile-app")
Certificate Rotation¶
Automate certificate renewal:
from provide.foundation.crypto.certificates import (
generate_self_signed_cert,
load_certificate,
)
from datetime import datetime, timedelta
from pathlib import Path
def rotate_certificate_if_needed(cert_path, key_path, days_before_expiry=30):
"""Rotate certificate if it's expiring soon."""
try:
cert = load_certificate(cert_path)
days_remaining = (cert.not_valid_after - datetime.now()).days
if days_remaining > days_before_expiry:
print(f"✅ Certificate valid for {days_remaining} days")
return False
print(f"⚠️ Certificate expiring in {days_remaining} days, rotating...")
except FileNotFoundError:
print("⚠️ Certificate not found, generating new one...")
# Generate new certificate
cert_pem, key_pem = generate_self_signed_cert(
common_name="example.com",
organization="Example Inc",
validity_days=365,
)
# Save new certificate
Path(cert_path).write_text(cert_pem)
Path(key_path).write_text(key_pem)
print("✅ Certificate rotated successfully")
return True
# Check and rotate if needed
rotate_certificate_if_needed("server.crt", "server.key")
Certificate for Service Mesh¶
Generate certificates for microservices:
from provide.foundation.crypto.certificates import generate_service_cert
def setup_service_mesh_certs(service_name, namespace="default"):
"""Generate certificates for Kubernetes service mesh."""
# Generate certificate with proper SAN for k8s DNS
cert_pem, key_pem = generate_service_cert(
common_name=f"{service_name}.{namespace}.svc.cluster.local",
organization="Service Mesh",
subject_alt_names=[
f"{service_name}",
f"{service_name}.{namespace}",
f"{service_name}.{namespace}.svc",
f"{service_name}.{namespace}.svc.cluster.local",
],
validity_days=90, # Shorter validity for security
)
# Save as Kubernetes secret format
import base64
secret_manifest = f"""
apiVersion: v1
kind: Secret
metadata:
name: {service_name}-tls
namespace: {namespace}
type: kubernetes.io/tls
data:
tls.crt: {base64.b64encode(cert_pem.encode()).decode()}
tls.key: {base64.b64encode(key_pem.encode()).decode()}
"""
Path(f"{service_name}-secret.yaml").write_text(secret_manifest)
print(f"✅ Generated certificate secret for {service_name}")
# Generate certs for services
setup_service_mesh_certs("user-service")
setup_service_mesh_certs("payment-service")
setup_service_mesh_certs("inventory-service")
Converting Certificate Formats¶
PEM to DER¶
Convert from PEM (text) to DER (binary):
from provide.foundation.crypto.certificates import pem_to_der
from pathlib import Path
# Load PEM certificate
pem_cert = Path("cert.pem").read_text()
# Convert to DER
der_cert = pem_to_der(pem_cert)
# Save DER certificate
Path("cert.der").write_bytes(der_cert)
Create PKCS#12 Bundle¶
Create a PKCS#12 (.p12/.pfx) file with certificate and private key:
from provide.foundation.crypto.certificates import create_pkcs12
# Create PKCS#12 bundle
p12_data = create_pkcs12(
certificate_pem=cert_pem,
private_key_pem=key_pem,
passphrase="secret-password",
friendly_name="My Certificate",
)
# Save to file
Path("certificate.p12").write_bytes(p12_data)
Best Practices¶
✅ DO: Use Appropriate Validity Periods¶
# ✅ Good: Reasonable validity periods
# Development
cert = generate_self_signed_cert(
common_name="dev.local",
validity_days=90, # 3 months for dev
)
# Production (with proper CA)
cert = generate_self_signed_cert(
common_name="prod.example.com",
validity_days=365, # 1 year max
)
# ❌ Bad: Too long validity
cert = generate_self_signed_cert(
common_name="example.com",
validity_days=3650, # 10 years - security risk!
)
✅ DO: Protect Private Keys¶
# ✅ Good: Secure file permissions
import os
from pathlib import Path
key_file = Path("private.key")
key_file.write_text(private_key_pem)
os.chmod(key_file, 0o600) # Read/write for owner only
# ✅ Good: Never log private keys
logger.info("Certificate generated", cert_path=cert_path) # OK
# ❌ Never do this:
# logger.info("Key generated", key=private_key_pem) # NEVER!
✅ DO: Use SAN for Multiple Hostnames¶
# ✅ Good: Proper SAN usage
cert = generate_self_signed_cert(
common_name="api.example.com",
subject_alt_names=[
"api.example.com",
"www.api.example.com",
"api-staging.example.com",
],
)
# ❌ Bad: Creating separate certs for each hostname
# More certs = more to manage and rotate
✅ DO: Monitor Certificate Expiration¶
# ✅ Good: Automated monitoring
from datetime import datetime, timedelta
def check_certificate_expiration(cert_path, warn_days=30):
"""Monitor certificate expiration."""
cert = load_certificate(cert_path)
expires = cert.not_valid_after
days_remaining = (expires - datetime.now()).days
if days_remaining < 0:
logger.critical("Certificate expired", cert_path=cert_path)
elif days_remaining < warn_days:
logger.warning(
"Certificate expiring soon",
cert_path=cert_path,
days_remaining=days_remaining,
)
else:
logger.info(
"Certificate valid",
cert_path=cert_path,
days_remaining=days_remaining,
)
return days_remaining
❌ DON'T: Use Self-Signed Certs in Production¶
# ❌ Bad: Self-signed in production
cert = generate_self_signed_cert(
common_name="production.example.com", # Don't!
)
# ✅ Good: Use proper CA for production
# - Let's Encrypt (free, automated)
# - DigiCert, GlobalSign, etc. (commercial)
# - Internal CA for private services
❌ DON'T: Commit Private Keys to Version Control¶
# ✅ Good: .gitignore
"""
*.key
*.pem
*.p12
*.pfx
certs/
"""
# ❌ Bad: Committing keys
# git add private.key # NEVER!
Certificate Management Tools¶
List Certificates¶
Get information about multiple certificates:
from provide.foundation.crypto.certificates import list_certificates
from pathlib import Path
def audit_certificates(cert_dir):
"""Audit all certificates in directory."""
cert_dir = Path(cert_dir)
for cert_file in cert_dir.glob("*.crt"):
info = get_certificate_info(cert_file)
print(f"\n📄 {cert_file.name}")
print(f" Subject: {info['subject']}")
print(f" Expires: {info['not_after']}")
# Check expiration
days = (info['not_after'] - datetime.now()).days
if days < 30:
print(f" ⚠️ Expiring in {days} days!")
else:
print(f" ✅ Valid for {days} days")
audit_certificates("certs/")
Validate Certificate Chain¶
Verify a certificate chain is valid:
from provide.foundation.crypto.certificates import validate_chain
def verify_cert_chain(server_cert, intermediate_cert, root_cert):
"""Verify certificate chain is valid."""
try:
is_valid = validate_chain(
server_cert=server_cert,
intermediate_cert=intermediate_cert,
root_cert=root_cert,
)
if is_valid:
print("✅ Certificate chain is valid")
else:
print("❌ Certificate chain is invalid")
return is_valid
except Exception as e:
logger.exception("Chain validation failed")
return False
Next Steps¶
Related Guides¶
- Key Generation: Generate cryptographic keys
- Signing & Verification: Sign and verify data
Examples¶
- See
examples/crypto/for certificate examples - See
examples/production/for TLS configuration patterns
API Reference¶
- API Reference: Crypto: Complete API documentation
Tip: For development, self-signed certificates are fine. For production, always use certificates from a trusted CA like Let's Encrypt (free and automated) or a commercial provider.