CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-certbot

ACME client that automates the process of obtaining, installing, and renewing SSL/TLS certificates from Let's Encrypt certificate authority.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

crypto-utilities.mddocs/

Cryptographic Utilities

Certificate and key generation, management, and validation functions for handling SSL/TLS certificates, private keys, and certificate signing requests.

Capabilities

Key Generation

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')

Certificate Signing Request Generation

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)

Certificate Information

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}")

Certificate Validation

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")

OCSP Support

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)")

Types

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

docs

crypto-utilities.md

display-ui.md

error-handling.md

index.md

main-config.md

plugin-development.md

utility-functions.md

tile.json