ACME client that automates the process of obtaining, installing, and renewing SSL/TLS certificates from Let's Encrypt certificate authority.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Certificate and key generation, management, and validation functions for handling SSL/TLS certificates, private keys, and certificate signing requests.
Generate private keys for certificate signing requests and SSL/TLS certificates.
def generate_key(key_size: int, key_dir: Optional[str], key_type: str = "rsa",
elliptic_curve: str = "secp256r1", keyname: str = "key-certbot.pem",
strict_permissions: bool = True) -> util.Key:
"""
Initialize and save a private key in PEM format.
Args:
key_size: Key size in bits if key type is RSA
key_dir: Optional directory to save key file
key_type: Key type ("rsa" or "ecdsa")
elliptic_curve: Name of elliptic curve if key type is ECDSA
keyname: Filename for the key (may be modified if file exists)
strict_permissions: If true, enforce 0700 permissions on key_dir
Returns:
Key object containing file path and PEM data
Raises:
ValueError: If unable to generate key with given parameters
"""
def make_key(bits: int = 2048, key_type: str = "rsa",
elliptic_curve: Optional[str] = None) -> bytes:
"""
Generate a private key and return PEM-encoded bytes.
Args:
bits: Key size in bits for RSA keys (minimum 2048)
key_type: Type of key to generate ("rsa" or "ecdsa")
elliptic_curve: Elliptic curve name for ECDSA keys
Returns:
PEM-encoded private key as bytes
Raises:
ValueError: If key generation parameters are invalid
"""
def valid_privkey(privkey: Union[str, bytes]) -> bool:
"""
Check if private key is valid and loadable.
Args:
privkey: Private key file contents in PEM format
Returns:
True if private key is valid and can be loaded
"""Usage examples:
from certbot import crypto_util
# Generate RSA key
rsa_key = crypto_util.generate_key(
key_size=2048,
key_dir='/etc/letsencrypt/keys',
key_type='rsa'
)
# Generate ECDSA key
ecdsa_key = crypto_util.generate_key(
key_size=256,
key_dir='/etc/letsencrypt/keys',
key_type='ecdsa',
elliptic_curve='secp256r1'
)
# Generate key without saving to file
key_pem = crypto_util.make_key(bits=2048, key_type='rsa')Create certificate signing requests (CSRs) for obtaining certificates from ACME CAs.
def generate_csr(privkey: util.Key, names: Union[list[str], set[str]],
path: Optional[str], must_staple: bool = False,
strict_permissions: bool = True) -> util.CSR:
def make_csr(private_key_pem: bytes, domains: list[str],
must_staple: bool = False) -> bytes:
"""
Generate a CSR and return DER-encoded bytes.
Args:
private_key_pem: PEM-encoded private key
domains: List of domain names for the certificate
must_staple: Whether to include OCSP Must-Staple extension
Returns:
DER-encoded CSR as bytes
"""
def valid_csr(csr: bytes) -> bool:
"""
Validate CSR with correct self-signed signature.
Args:
csr: CSR in PEM format
Returns:
True if CSR is valid with correct signature
"""
def csr_matches_pubkey(csr: bytes, privkey: bytes) -> bool:
"""
Check if private key corresponds to the CSR's public key.
Args:
csr: CSR in PEM format
privkey: Private key file contents in PEM format
Returns:
True if private key matches CSR public key
"""
def import_csr_file(csrfile: str, data: bytes) -> tuple[acme_crypto_util.Format, util.CSR, list[str]]:
"""
Import a CSR file which can be either PEM or DER format.
Args:
csrfile: CSR filename
data: Contents of the CSR file
Returns:
Tuple of (Format.PEM, CSR object, list of domains requested)
Raises:
errors.Error: If CSR file cannot be parsed
"""The generate_csr function creates certificate signing requests for ACME certificate requests:
def generate_csr(privkey: util.Key, names: Union[list[str], set[str]],
path: Optional[str], must_staple: bool = False,
strict_permissions: bool = True) -> util.CSR:
"""
Generate a certificate signing request.
Args:
privkey: Private key to sign the CSR
names: Domain names to include in the CSR (first is CN, rest are SANs)
path: Optional path to save CSR file
must_staple: Whether to include OCSP Must-Staple extension
strict_permissions: If true, enforce 0755 permissions on path directory
Returns:
CSR object containing file path, data, and format
Raises:
ValueError: If names list is empty or invalid
"""
def make_csr(private_key_pem: bytes, domains: list[str],
must_staple: bool = False) -> bytes:
"""
Generate a CSR and return DER-encoded bytes.
Args:
private_key_pem: PEM-encoded private key
domains: List of domain names for the certificate
must_staple: Whether to include OCSP Must-Staple extension
Returns:
DER-encoded CSR as bytes
"""Usage examples:
from certbot import crypto_util, util
# Generate key first
key = crypto_util.generate_key(2048, '/etc/letsencrypt/keys')
# Generate CSR for multiple domains
domains = ['example.com', 'www.example.com', 'api.example.com']
csr = crypto_util.generate_csr(
privkey=key,
names=domains,
path='/etc/letsencrypt/csr/example.csr',
must_staple=True
)
# Generate CSR without saving to file
csr_data = crypto_util.make_csr(key.pem, domains, must_staple=False)Extract information and validate certificates.
def notAfter(cert_path: str) -> datetime:
"""
Get certificate expiration date.
Args:
cert_path: Path to certificate file
Returns:
Certificate expiration datetime
Raises:
errors.Error: If certificate cannot be read
"""
def notBefore(cert_path: str) -> datetime:
"""
Get certificate valid from date.
Args:
cert_path: Path to certificate file
Returns:
Certificate valid from datetime
Raises:
errors.Error: If certificate cannot be read
"""
def cert_from_chain(chain_path: str) -> x509.Certificate:
"""
Extract the first certificate from a certificate chain file.
Args:
chain_path: Path to certificate chain file
Returns:
X.509 certificate object
Raises:
errors.Error: If chain file cannot be read or parsed
"""
def get_sans_from_cert(cert_path: str, typ: type = x509.DNSName) -> list[str]:
"""
Get Subject Alternative Names from certificate.
Args:
cert_path: Path to certificate file
typ: Type of SAN to extract (default: DNS names)
Returns:
List of SAN values
"""Usage examples:
from certbot import crypto_util
from datetime import datetime, timezone
# Check certificate expiration
cert_path = '/etc/letsencrypt/live/example.com/cert.pem'
expiry = crypto_util.notAfter(cert_path)
if expiry < datetime.now(timezone.utc):
print("Certificate has expired")
# Get certificate validity period
valid_from = crypto_util.notBefore(cert_path)
valid_until = crypto_util.notAfter(cert_path)
# Extract certificate from chain
chain_path = '/etc/letsencrypt/live/example.com/chain.pem'
cert = crypto_util.cert_from_chain(chain_path)
# Get domain names from certificate
domains = crypto_util.get_sans_from_cert(cert_path)
print(f"Certificate covers domains: {domains}")Validate certificates and certificate chains.
def valid_privkey(privkey_path: str) -> bool:
"""
Check if private key is valid and loadable.
Args:
privkey_path: Path to private key file
Returns:
True if private key is valid
"""
def verify_cert_matches_priv_key(cert_path: str, key_path: str) -> bool:
"""
Verify that certificate matches private key.
Args:
cert_path: Path to certificate file
key_path: Path to private key file
Returns:
True if certificate and key match
Raises:
errors.Error: If files cannot be read
"""
def cert_chain_matches(cert_path: str, chain_path: str) -> bool:
"""
Verify that certificate chain is valid for certificate.
Args:
cert_path: Path to certificate file
chain_path: Path to certificate chain file
Returns:
True if chain is valid for certificate
"""
def verify_renewable_cert(renewable_cert: interfaces.RenewableCert) -> None:
"""
Comprehensive verification of a renewable certificate.
Checks signature verification, fullchain integrity, and key matching.
Args:
renewable_cert: Certificate to verify
Raises:
errors.Error: If verification fails
"""
def verify_renewable_cert_sig(renewable_cert: interfaces.RenewableCert) -> None:
"""
Verify the signature of a RenewableCert object.
Args:
renewable_cert: Certificate to verify
Raises:
errors.Error: If signature verification fails
"""
def verify_fullchain(renewable_cert: interfaces.RenewableCert) -> None:
"""
Verify that fullchain is cert concatenated with chain.
Args:
renewable_cert: Certificate to verify
Raises:
errors.Error: If cert and chain do not combine to fullchain
"""
def get_names_from_cert(cert: bytes, typ: Union[acme_crypto_util.Format, int] = acme_crypto_util.Format.PEM) -> list[str]:
"""
Get all domain names from a certificate including CN.
Args:
cert: Certificate in encoded format
typ: Format of the cert bytes (PEM or DER)
Returns:
List of domain names from certificate
"""
def get_names_from_req(csr: bytes, typ: Union[acme_crypto_util.Format, int] = acme_crypto_util.Format.PEM) -> list[str]:
"""
Get domain names from a CSR including CN.
Args:
csr: CSR in encoded format
typ: Format of the CSR bytes (PEM or DER)
Returns:
List of domain names from CSR
"""
def sha256sum(filename: str) -> str:
"""
Compute SHA256 hash of a file.
Args:
filename: Path to file to hash
Returns:
SHA256 digest in hexadecimal format
"""
def cert_and_chain_from_fullchain(fullchain_pem: str) -> tuple[str, str]:
"""
Split fullchain PEM into separate cert and chain PEMs.
Args:
fullchain_pem: Concatenated certificate + chain
Returns:
Tuple of (cert_pem, chain_pem)
Raises:
errors.Error: If less than 2 certificates in chain
"""
def get_serial_from_cert(cert_path: str) -> int:
"""
Get certificate serial number.
Args:
cert_path: Path to certificate file
Returns:
Certificate serial number
"""
def find_chain_with_issuer(fullchains: list[str], issuer_cn: str,
warn_on_no_match: bool = False) -> str:
"""
Find certificate chain with matching issuer common name.
Args:
fullchains: List of fullchains in PEM format
issuer_cn: Exact Subject Common Name to match
warn_on_no_match: Whether to warn if no chain matches
Returns:
Best-matching fullchain or first if none match
"""Usage examples:
from certbot import crypto_util
cert_path = '/etc/letsencrypt/live/example.com/cert.pem'
key_path = '/etc/letsencrypt/live/example.com/privkey.pem'
chain_path = '/etc/letsencrypt/live/example.com/chain.pem'
# Validate private key
if not crypto_util.valid_privkey(key_path):
print("Private key is invalid")
# Verify certificate matches private key
if crypto_util.verify_cert_matches_priv_key(cert_path, key_path):
print("Certificate and private key match")
# Verify certificate chain
if crypto_util.cert_chain_matches(cert_path, chain_path):
print("Certificate chain is valid")Check certificate revocation status using OCSP.
class RevocationChecker:
"""OCSP revocation checking functionality."""
def ocsp_revoked(self, cert: RenewableCert) -> bool:
"""
Get revocation status for a certificate.
Args:
cert: Certificate object to check
Returns:
True if revoked; False if valid or check failed
"""
def ocsp_revoked_by_paths(self, cert_path: str, chain_path: str,
timeout: int = 10) -> bool:
"""
Check revocation status using file paths.
Args:
cert_path: Path to certificate file
chain_path: Path to certificate chain file
timeout: Timeout in seconds for OCSP query
Returns:
True if revoked; False if valid or check failed
"""Usage example:
from certbot import ocsp
# Create revocation checker
checker = ocsp.RevocationChecker()
# Check revocation by file paths
cert_path = '/etc/letsencrypt/live/example.com/cert.pem'
chain_path = '/etc/letsencrypt/live/example.com/chain.pem'
if checker.ocsp_revoked_by_paths(cert_path, chain_path):
print("Certificate has been revoked")
else:
print("Certificate is valid (not revoked)")class Key(NamedTuple):
"""Container for private key data."""
file: Optional[str] # Path to key file (None if not saved)
pem: bytes # PEM-encoded key data
class CSR(NamedTuple):
"""Container for certificate signing request data."""
file: Optional[str] # Path to CSR file (None if not saved)
data: bytes # CSR data (PEM or DER encoded)
form: str # Format of data ("pem" or "der")Install with Tessl CLI
npx tessl i tessl/pypi-certbot