Skip to content

Index

๐Ÿค– AI-Generated Content

This documentation was generated with AI assistance and is still being audited. Some, or potentially a lot, of this information may be inaccurate. Learn more.

provide.foundation.crypto.certificates

Classes

Certificate

X.509 certificate management using attrs.

This class should be instantiated via factory methods: - Certificate.from_pem() - Load from PEM strings - Certificate.generate() - Generate new certificate - Certificate.create_ca() - Generate CA certificate - Certificate.create_signed_certificate() - Generate signed certificate - Certificate.create_self_signed_server_cert() - Generate self-signed server cert - Certificate.create_self_signed_client_cert() - Generate self-signed client cert

Attributes
is_ca property
is_ca: bool

Checks if the certificate has the Basic Constraints CA flag set to True.

is_valid cached property
is_valid: bool

Checks if the certificate is currently valid based on its dates.

issuer property
issuer: str

Returns the certificate issuer as an RFC4514 string.

public_key property
public_key: PublicKey | None

Returns the public key object from the certificate.

serial_number property
serial_number: int | None

Returns the certificate serial number.

subject property
subject: str

Returns the certificate subject as an RFC4514 string.

trust_chain property writable
trust_chain: list[Certificate]

Returns the list of trusted certificates associated with this one.

Functions
__eq__
__eq__(other: object) -> bool

Custom equality based on subject and serial number.

Source code in provide/foundation/crypto/certificates/certificate.py
def __eq__(self, other: object) -> bool:
    """Custom equality based on subject and serial number."""
    if not isinstance(other, Certificate):
        return NotImplemented
    if not hasattr(self, "_base") or not hasattr(other, "_base"):
        return False
    eq = (
        self._base.subject == other._base.subject and self._base.serial_number == other._base.serial_number
    )
    return eq
__hash__
__hash__() -> int

Custom hash based on subject and serial number.

Source code in provide/foundation/crypto/certificates/certificate.py
def __hash__(self) -> int:
    """Custom hash based on subject and serial number."""
    if not hasattr(self, "_base"):
        logger.warning("๐Ÿ“œ๐Ÿ”โš ๏ธ __hash__ called before _base initialized")
        return hash((None, None))

    h = hash((self._base.subject, self._base.serial_number))
    return h
create_ca classmethod
create_ca(
    common_name: str,
    organization_name: str,
    validity_days: int,
    key_type: str = DEFAULT_CERTIFICATE_KEY_TYPE,
    key_size: int = DEFAULT_RSA_KEY_SIZE,
    ecdsa_curve: str = DEFAULT_CERTIFICATE_CURVE,
) -> Certificate

Creates a new self-signed CA certificate.

Source code in provide/foundation/crypto/certificates/certificate.py
@classmethod
def create_ca(
    cls,
    common_name: str,
    organization_name: str,
    validity_days: int,
    key_type: str = DEFAULT_CERTIFICATE_KEY_TYPE,
    key_size: int = DEFAULT_RSA_KEY_SIZE,
    ecdsa_curve: str = DEFAULT_CERTIFICATE_CURVE,
) -> Certificate:
    """Creates a new self-signed CA certificate."""
    from provide.foundation.crypto.certificates.factory import create_ca_certificate

    return create_ca_certificate(
        common_name,
        organization_name,
        validity_days,
        key_type,
        key_size,
        ecdsa_curve,
    )
create_self_signed_client_cert classmethod
create_self_signed_client_cert(
    common_name: str,
    organization_name: str,
    validity_days: int,
    alt_names: list[str] | None = None,
    key_type: str = DEFAULT_CERTIFICATE_KEY_TYPE,
    key_size: int = DEFAULT_RSA_KEY_SIZE,
    ecdsa_curve: str = DEFAULT_CERTIFICATE_CURVE,
) -> Certificate

Creates a new self-signed end-entity certificate suitable for a client.

Source code in provide/foundation/crypto/certificates/certificate.py
@classmethod
def create_self_signed_client_cert(
    cls,
    common_name: str,
    organization_name: str,
    validity_days: int,
    alt_names: list[str] | None = None,
    key_type: str = DEFAULT_CERTIFICATE_KEY_TYPE,
    key_size: int = DEFAULT_RSA_KEY_SIZE,
    ecdsa_curve: str = DEFAULT_CERTIFICATE_CURVE,
) -> Certificate:
    """Creates a new self-signed end-entity certificate suitable for a client."""
    from provide.foundation.crypto.certificates.factory import (
        create_self_signed_client_cert,
    )

    return create_self_signed_client_cert(
        common_name,
        organization_name,
        validity_days,
        alt_names,
        key_type,
        key_size,
        ecdsa_curve,
    )
create_self_signed_server_cert classmethod
create_self_signed_server_cert(
    common_name: str,
    organization_name: str,
    validity_days: int,
    alt_names: list[str] | None = None,
    key_type: str = DEFAULT_CERTIFICATE_KEY_TYPE,
    key_size: int = DEFAULT_RSA_KEY_SIZE,
    ecdsa_curve: str = DEFAULT_CERTIFICATE_CURVE,
) -> Certificate

Creates a new self-signed end-entity certificate suitable for a server.

Source code in provide/foundation/crypto/certificates/certificate.py
@classmethod
def create_self_signed_server_cert(
    cls,
    common_name: str,
    organization_name: str,
    validity_days: int,
    alt_names: list[str] | None = None,
    key_type: str = DEFAULT_CERTIFICATE_KEY_TYPE,
    key_size: int = DEFAULT_RSA_KEY_SIZE,
    ecdsa_curve: str = DEFAULT_CERTIFICATE_CURVE,
) -> Certificate:
    """Creates a new self-signed end-entity certificate suitable for a server."""
    from provide.foundation.crypto.certificates.factory import (
        create_self_signed_server_cert,
    )

    return create_self_signed_server_cert(
        common_name,
        organization_name,
        validity_days,
        alt_names,
        key_type,
        key_size,
        ecdsa_curve,
    )
create_signed_certificate classmethod
create_signed_certificate(
    ca_certificate: Certificate,
    common_name: str,
    organization_name: str,
    validity_days: int,
    alt_names: list[str] | None = None,
    key_type: str = DEFAULT_CERTIFICATE_KEY_TYPE,
    key_size: int = DEFAULT_RSA_KEY_SIZE,
    ecdsa_curve: str = DEFAULT_CERTIFICATE_CURVE,
    is_client_cert: bool = False,
) -> Certificate

Creates a new certificate signed by the provided CA certificate.

Source code in provide/foundation/crypto/certificates/certificate.py
@classmethod
def create_signed_certificate(
    cls,
    ca_certificate: Certificate,
    common_name: str,
    organization_name: str,
    validity_days: int,
    alt_names: list[str] | None = None,
    key_type: str = DEFAULT_CERTIFICATE_KEY_TYPE,
    key_size: int = DEFAULT_RSA_KEY_SIZE,
    ecdsa_curve: str = DEFAULT_CERTIFICATE_CURVE,
    is_client_cert: bool = False,
) -> Certificate:
    """Creates a new certificate signed by the provided CA certificate."""
    from provide.foundation.crypto.certificates.factory import (
        create_signed_certificate,
    )

    return create_signed_certificate(
        ca_certificate,
        common_name,
        organization_name,
        validity_days,
        alt_names,
        key_type,
        key_size,
        ecdsa_curve,
        is_client_cert,
    )
from_pem classmethod
from_pem(
    cert_pem: str, key_pem: str | None = None
) -> Certificate

Load certificate from PEM strings.

Parameters:

Name Type Description Default
cert_pem str

Certificate in PEM format (string or URI)

required
key_pem str | None

Optional private key in PEM format (string or URI)

None

Returns:

Type Description
Certificate

Certificate instance

Raises:

Type Description
CertificateError

If loading fails

Example

cert = Certificate.from_pem(cert_pem_string, key_pem_string) assert cert.is_valid

Source code in provide/foundation/crypto/certificates/certificate.py
@classmethod
def from_pem(cls, cert_pem: str, key_pem: str | None = None) -> Certificate:
    """Load certificate from PEM strings.

    Args:
        cert_pem: Certificate in PEM format (string or URI)
        key_pem: Optional private key in PEM format (string or URI)

    Returns:
        Certificate instance

    Raises:
        CertificateError: If loading fails

    Example:
        >>> cert = Certificate.from_pem(cert_pem_string, key_pem_string)
        >>> assert cert.is_valid
    """
    base, x509_cert, private_key, cert_pem_str, key_pem_str = load_certificate_from_pem(
        cert_pem,
        key_pem,
    )

    # Extract metadata from x509 cert subject
    try:
        cn_attr = x509_cert.subject.get_attributes_for_oid(x509.oid.NameOID.COMMON_NAME)[0]
        common_name = cn_attr.value
    except (IndexError, AttributeError):
        common_name = "Unknown"

    try:
        org_attr = x509_cert.subject.get_attributes_for_oid(x509.oid.NameOID.ORGANIZATION_NAME)[0]
        organization_name = org_attr.value
    except (IndexError, AttributeError):
        organization_name = "Unknown"

    # Calculate validity days from certificate dates
    validity_delta = base.not_valid_after - base.not_valid_before
    validity_days = validity_delta.days

    # Determine key type from public key
    if isinstance(base.public_key, rsa.RSAPublicKey):
        key_type = "rsa"
    elif isinstance(base.public_key, ec.EllipticCurvePublicKey):
        key_type = "ecdsa"
    else:
        key_type = "unknown"

    return cls(
        _base=base,
        _cert=x509_cert,
        _private_key=private_key,
        cert_pem=cert_pem_str,
        key_pem=key_pem_str,
        common_name=common_name,
        organization_name=organization_name,
        validity_days=validity_days,
        key_type=key_type,
    )
generate classmethod
generate(
    common_name: str = DEFAULT_CERTIFICATE_COMMON_NAME,
    organization_name: str = DEFAULT_CERTIFICATE_ORGANIZATION_NAME,
    validity_days: int = DEFAULT_CERTIFICATE_VALIDITY_DAYS,
    key_type: str = DEFAULT_CERTIFICATE_KEY_TYPE,
    key_size: int = DEFAULT_RSA_KEY_SIZE,
    ecdsa_curve: str = DEFAULT_CERTIFICATE_CURVE,
    alt_names: list[str] | None = None,
    is_ca: bool = False,
    is_client_cert: bool = True,
) -> Certificate

Generate a new certificate with a new keypair.

Parameters:

Name Type Description Default
common_name str

Certificate common name

DEFAULT_CERTIFICATE_COMMON_NAME
organization_name str

Organization name

DEFAULT_CERTIFICATE_ORGANIZATION_NAME
validity_days int

Number of days certificate is valid

DEFAULT_CERTIFICATE_VALIDITY_DAYS
key_type str

Key type ("rsa" or "ecdsa")

DEFAULT_CERTIFICATE_KEY_TYPE
key_size int

RSA key size in bits

DEFAULT_RSA_KEY_SIZE
ecdsa_curve str

ECDSA curve name

DEFAULT_CERTIFICATE_CURVE
alt_names list[str] | None

Subject alternative names

None
is_ca bool

Whether this is a CA certificate

False
is_client_cert bool

Whether this is a client certificate

True

Returns:

Type Description
Certificate

New Certificate instance

Example

cert = Certificate.generate( ... common_name="example.com", ... organization_name="Example Corp", ... ) assert cert._private_key is not None

Source code in provide/foundation/crypto/certificates/certificate.py
@classmethod
def generate(
    cls,
    common_name: str = DEFAULT_CERTIFICATE_COMMON_NAME,
    organization_name: str = DEFAULT_CERTIFICATE_ORGANIZATION_NAME,
    validity_days: int = DEFAULT_CERTIFICATE_VALIDITY_DAYS,
    key_type: str = DEFAULT_CERTIFICATE_KEY_TYPE,
    key_size: int = DEFAULT_RSA_KEY_SIZE,
    ecdsa_curve: str = DEFAULT_CERTIFICATE_CURVE,
    alt_names: list[str] | None = None,
    is_ca: bool = False,
    is_client_cert: bool = True,
) -> Certificate:
    """Generate a new certificate with a new keypair.

    Args:
        common_name: Certificate common name
        organization_name: Organization name
        validity_days: Number of days certificate is valid
        key_type: Key type ("rsa" or "ecdsa")
        key_size: RSA key size in bits
        ecdsa_curve: ECDSA curve name
        alt_names: Subject alternative names
        is_ca: Whether this is a CA certificate
        is_client_cert: Whether this is a client certificate

    Returns:
        New Certificate instance

    Example:
        >>> cert = Certificate.generate(
        ...     common_name="example.com",
        ...     organization_name="Example Corp",
        ... )
        >>> assert cert._private_key is not None
    """
    alt_names = alt_names or default_certificate_alt_names()

    base, x509_cert, private_key, cert_pem, key_pem = generate_certificate(
        common_name=common_name,
        organization_name=organization_name,
        validity_days=validity_days,
        key_type=key_type,
        key_size=key_size,
        ecdsa_curve=ecdsa_curve,
        alt_names=alt_names,
        is_ca=is_ca,
        is_client_cert=is_client_cert,
    )

    return cls(
        _base=base,
        _cert=x509_cert,
        _private_key=private_key,
        cert_pem=cert_pem,
        key_pem=key_pem,
        common_name=common_name,
        organization_name=organization_name,
        validity_days=validity_days,
        key_type=key_type,
        key_size=key_size,
        ecdsa_curve=ecdsa_curve,
        alt_names=alt_names,
    )
verify_trust
verify_trust(other_cert: Self) -> bool

Verifies if the other_cert is trusted based on this certificate's trust chain.

Source code in provide/foundation/crypto/certificates/certificate.py
def verify_trust(self, other_cert: Self) -> bool:
    """Verifies if the `other_cert` is trusted based on this certificate's trust chain."""
    return verify_trust_impl(self, other_cert, self._trust_chain)

CertificateBase

Immutable base certificate data.

Functions
create classmethod
create(config: CertificateConfig) -> tuple[Self, KeyPair]

Create a new certificate base and private key.

Source code in provide/foundation/crypto/certificates/base.py
@classmethod
def create(cls, config: CertificateConfig) -> tuple[Self, KeyPair]:
    """Create a new certificate base and private key."""
    _require_crypto()
    try:
        logger.debug("๐Ÿ“œ๐Ÿ“๐Ÿš€ CertificateBase.create: Starting base creation")
        not_valid_before = config["not_valid_before"]
        not_valid_after = config["not_valid_after"]

        if not_valid_before.tzinfo is None:
            not_valid_before = not_valid_before.replace(tzinfo=UTC)
        if not_valid_after.tzinfo is None:
            not_valid_after = not_valid_after.replace(tzinfo=UTC)

        logger.debug(
            "๐Ÿ“œ๐Ÿ“… Certificate validity dates configured",
            not_valid_before=not_valid_before.isoformat(),
            not_valid_after=not_valid_after.isoformat(),
        )

        private_key: KeyPair
        match config["key_type"]:
            case KeyType.RSA:
                key_size = config.get("key_size", DEFAULT_RSA_KEY_SIZE)
                logger.debug(f"๐Ÿ“œ๐Ÿ”‘๐Ÿš€ Generating RSA key (size: {key_size})")
                private_key = rsa.generate_private_key(public_exponent=65537, key_size=key_size)
            case KeyType.ECDSA:
                curve_choice = config.get("curve", CurveType.SECP384R1)
                logger.debug(f"๐Ÿ“œ๐Ÿ”‘๐Ÿš€ Generating ECDSA key (curve: {curve_choice})")
                curve = getattr(ec, curve_choice.name)()
                private_key = ec.generate_private_key(curve)
            case _:
                raise ValueError(f"Internal Error: Unsupported key type: {config['key_type']}")

        subject = cls._create_name(config["common_name"], config["organization"])
        issuer = cls._create_name(config["common_name"], config["organization"])

        serial_number = x509.random_serial_number()

        base = cls(
            subject=subject,
            issuer=issuer,
            public_key=private_key.public_key(),
            not_valid_before=not_valid_before,
            not_valid_after=not_valid_after,
            serial_number=serial_number,
        )
        return base, private_key

    except Exception as e:
        logger.error(
            f"๐Ÿ“œโŒ CertificateBase.create: Failed: {e}",
            extra={"error": str(e), "trace": traceback.format_exc()},
        )
        raise CertificateError(f"Failed to generate certificate base: {e}") from e

CertificateError

CertificateError(message: str, hint: str | None = None)

Bases: ValidationError

Certificate-related errors.

Source code in provide/foundation/crypto/certificates/base.py
def __init__(self, message: str, hint: str | None = None) -> None:
    super().__init__(
        message=message,
        field="certificate",
        value=None,
        rule=hint or "Certificate operation failed",
    )

Functions

create_ca

create_ca(
    common_name: str,
    organization: str = "Default CA Organization",
    validity_days: int = DEFAULT_CERTIFICATE_VALIDITY_DAYS
    * 2,
    key_type: str = DEFAULT_CERTIFICATE_KEY_TYPE,
) -> Certificate

Create a CA certificate (convenience function).

Source code in provide/foundation/crypto/certificates/factory.py
def create_ca(
    common_name: str,
    organization: str = "Default CA Organization",
    validity_days: int = DEFAULT_CERTIFICATE_VALIDITY_DAYS * 2,  # CAs live longer
    key_type: str = DEFAULT_CERTIFICATE_KEY_TYPE,
) -> Certificate:
    """Create a CA certificate (convenience function)."""
    _require_crypto()
    return create_ca_certificate(
        common_name=common_name,
        organization_name=organization,
        validity_days=validity_days,
        key_type=key_type,
    )

create_self_signed

create_self_signed(
    common_name: str = "localhost",
    alt_names: list[str] | None = None,
    organization: str = "Default Organization",
    validity_days: int = DEFAULT_CERTIFICATE_VALIDITY_DAYS,
    key_type: str = DEFAULT_CERTIFICATE_KEY_TYPE,
) -> Certificate

Create a self-signed certificate (convenience function).

Source code in provide/foundation/crypto/certificates/factory.py
def create_self_signed(
    common_name: str = "localhost",
    alt_names: list[str] | None = None,
    organization: str = "Default Organization",
    validity_days: int = DEFAULT_CERTIFICATE_VALIDITY_DAYS,
    key_type: str = DEFAULT_CERTIFICATE_KEY_TYPE,
) -> Certificate:
    """Create a self-signed certificate (convenience function)."""
    _require_crypto()
    return create_self_signed_server_cert(
        common_name=common_name,
        organization_name=organization,
        validity_days=validity_days,
        alt_names=alt_names or [common_name],
        key_type=key_type,
    )

create_x509_certificate

create_x509_certificate(
    base: CertificateBase,
    private_key: KeyPair,
    alt_names: list[str] | None = None,
    issuer_name_override: Name | None = None,
    signing_key_override: KeyPair | None = None,
    is_ca: bool = False,
    is_client_cert: bool = False,
) -> X509Certificate

Internal helper to build and sign the X.509 certificate object.

Source code in provide/foundation/crypto/certificates/operations.py
def create_x509_certificate(
    base: CertificateBase,
    private_key: KeyPair,
    alt_names: list[str] | None = None,
    issuer_name_override: x509.Name | None = None,
    signing_key_override: KeyPair | None = None,
    is_ca: bool = False,
    is_client_cert: bool = False,
) -> X509Certificate:
    """Internal helper to build and sign the X.509 certificate object."""
    try:
        logger.debug("๐Ÿ“œ๐Ÿ“๐Ÿš€ create_x509_certificate: Building certificate")

        actual_issuer_name = issuer_name_override if issuer_name_override else base.issuer
        actual_signing_key = signing_key_override if signing_key_override else private_key

        if not actual_signing_key:
            raise CertificateError("Cannot sign certificate without a signing key (either own or override)")

        builder = (
            x509.CertificateBuilder()
            .subject_name(base.subject)
            .issuer_name(actual_issuer_name)
            .public_key(base.public_key)
            .serial_number(base.serial_number)
            .not_valid_before(base.not_valid_before)
            .not_valid_after(base.not_valid_after)
        )

        san_list = [x509.DNSName(name) for name in (alt_names or []) if name]
        if san_list:
            # DNSName is a subtype of GeneralName, but mypy needs help understanding this
            builder = builder.add_extension(x509.SubjectAlternativeName(cast(list, san_list)), critical=False)

        builder = builder.add_extension(
            x509.BasicConstraints(ca=is_ca, path_length=None),
            critical=True,
        )

        if is_ca:
            builder = builder.add_extension(
                x509.KeyUsage(
                    digital_signature=False,
                    key_encipherment=False,
                    key_agreement=False,
                    content_commitment=False,
                    data_encipherment=False,
                    key_cert_sign=True,
                    crl_sign=True,
                    encipher_only=False,
                    decipher_only=False,
                ),
                critical=True,
            )
        else:
            builder = builder.add_extension(
                x509.KeyUsage(
                    digital_signature=True,
                    key_encipherment=(
                        bool(not is_client_cert and isinstance(base.public_key, rsa.RSAPublicKey))
                    ),
                    key_agreement=(bool(isinstance(base.public_key, ec.EllipticCurvePublicKey))),
                    content_commitment=False,
                    data_encipherment=False,
                    key_cert_sign=False,
                    crl_sign=False,
                    encipher_only=False,
                    decipher_only=False,
                ),
                critical=True,
            )
            extended_usages = []
            if is_client_cert:
                extended_usages.append(ExtendedKeyUsageOID.CLIENT_AUTH)
            else:
                extended_usages.append(ExtendedKeyUsageOID.SERVER_AUTH)

            if extended_usages:
                builder = builder.add_extension(
                    x509.ExtendedKeyUsage(extended_usages),
                    critical=False,
                )

        logger.debug(
            f"KeyUsage, ExtendedKeyUsage (is_client_cert={is_client_cert})",
        )

        signed_cert = builder.sign(
            private_key=actual_signing_key,
            algorithm=hashes.SHA256(),
        )
        return signed_cert

    except Exception as e:
        logger.error(
            f"๐Ÿ“œโŒ create_x509_certificate: Failed: {e}",
            extra={"error": str(e), "trace": traceback.format_exc()},
        )
        raise CertificateError("Failed to create X.509 certificate object") from e

validate_signature

validate_signature(
    signed_cert_obj: Certificate,
    signing_cert_obj: Certificate,
    signing_public_key: PublicKey,
) -> bool

Internal helper: Validates signature and issuer/subject match.

Source code in provide/foundation/crypto/certificates/operations.py
def validate_signature(
    signed_cert_obj: X509Certificate,
    signing_cert_obj: X509Certificate,
    signing_public_key: PublicKey,
) -> bool:
    """Internal helper: Validates signature and issuer/subject match."""
    if signed_cert_obj.issuer != signing_cert_obj.subject:
        logger.debug(
            f"๐Ÿ“œ๐Ÿ”โŒ Signature validation failed: Issuer/Subject mismatch. "
            f"Signed Issuer='{signed_cert_obj.issuer}', "
            f"Signing Subject='{signing_cert_obj.subject}'",
        )
        return False

    try:
        if not signing_public_key:
            logger.error("๐Ÿ“œ๐Ÿ”โŒ Cannot validate signature: Signing certificate has no public key")
            return False

        signature = signed_cert_obj.signature
        tbs_certificate_bytes = signed_cert_obj.tbs_certificate_bytes
        signature_hash_algorithm = signed_cert_obj.signature_hash_algorithm

        if not signature_hash_algorithm:
            logger.error("๐Ÿ“œ๐Ÿ”โŒ Cannot validate signature: Unknown hash algorithm")
            return False

        if isinstance(signing_public_key, rsa.RSAPublicKey):
            cast("rsa.RSAPublicKey", signing_public_key).verify(
                signature,
                tbs_certificate_bytes,
                padding.PKCS1v15(),
                signature_hash_algorithm,
            )
        elif isinstance(signing_public_key, ec.EllipticCurvePublicKey):
            cast("ec.EllipticCurvePublicKey", signing_public_key).verify(
                signature,
                tbs_certificate_bytes,
                ec.ECDSA(signature_hash_algorithm),
            )
        else:
            logger.error(f"๐Ÿ“œ๐Ÿ”โŒ Unsupported signing public key type: {type(signing_public_key)}")
            return False

        return True

    except Exception as e:
        logger.debug(f"๐Ÿ“œ๐Ÿ”โŒ Signature validation failed: {type(e).__name__}: {e}")
        return False