tiny_ca.ca_factory.factory module¶
factory.py
X.509 certificate factory: issues end-entity certificates, builds self-signed CA certificates, generates CRLs, and validates certificate chains.
This module contains a single public class, CertificateFactory, whose only
responsibility is cryptographic construction. It never touches the filesystem,
the database, or any business-logic layer.
SOLID notes¶
- SRP
CertificateFactoryhandles cryptographic generation only. File persistence →
BaseStorage. DB registration →BaseDB.- OCPNew X.509 extension types are added in
_build_extensionswithout modifying any other method.
LSP : Any ICALoader implementation can be swapped in transparently.
ISP : SerialWithEncoding and CertLifetime are separate utility modules;
only the symbols needed here are imported.
- DIPCA material is provided through the
ICALoaderProtocol injected at construction time; the factory never instantiates a loader itself.
- class tiny_ca.ca_factory.factory.CertificateFactory(ca_loader, logger=None)[source]¶
Bases:
objectCryptographic factory for X.509 certificates, CSRs, and CRLs.
CertificateFactoryis the single source of all certificate-generation logic in the library. It accepts anICALoaderat construction time and uses the CA certificate and key it provides to sign all issued artefacts.Responsibilities¶
Generate self-signed root CA certificates (
build_self_signed_ca).Issue end-entity certificates signed by the loaded CA (
issue_certificate).Build and sign Certificate Revocation Lists (
build_crl).Validate an existing certificate against the loaded CA (
validate_cert).
Out of scope¶
Writing any files to disk.
Recording certificates in a database.
Business-level rules (duplicate CN detection, rotation policies, etc.).
- type ca_loader:
- param ca_loader:
Provider of the CA certificate, private key, and base Subject info. Must satisfy the
ICALoaderProtocol (seefile_loader.py).- type ca_loader:
ICALoader
- type logger:
- param logger:
Logger for operational messages. Falls back to
DEFAULT_LOGGERwhenNone.- type logger:
Logger | None
- raises TypeError:
If ca_loader does not implement the
ICALoaderProtocol.
- build_crl(revoked_certs, days_valid=1)[source]¶
Build and sign a Certificate Revocation List from the provided records.
Iterates over revoked_certs, adds each entry to the CRL builder, then signs the list with the CA private key. The resulting CRL is valid from the current UTC time until
now + days_validdays.- Parameters:
revoked_certs (Generator[CertificateRecord, None, None]) – Iterable of revoked certificate records as returned by
BaseDB.get_revoked_certificates. Each record must exposeserial_number(castable toint) andrevocation_date(adatetimeobject).days_valid (int) – Number of days until the CRL expires and must be regenerated. Typical values are
1(daily rotation) to7(weekly). Default:1.
- Returns:
The signed CRL object. The caller is responsible for persisting it to storage via
BaseStorage.- Return type:
x509.CertificateRevocationList
- static build_self_signed_ca(common_name='Internal CA', organization='My Company', country='UA', key_size=2048, days_valid=3650, valid_from=None, logger=None)[source]¶
Generate a self-signed root CA certificate and its private key.
This is a
@staticmethod— it requires no loaded CA because the resulting certificate is its own issuer. It is typically called once during bootstrap to establish the trust anchor for the PKI.The generated certificate includes: -
BasicConstraints(ca=True)— marks it as a CA certificate. -KeyUsagewithkey_cert_signandcrl_signset toTrue. -SubjectKeyIdentifierderived from the public key.- Parameters:
common_name (str) – Common Name (CN) for the CA Subject / Issuer fields. Default:
"Internal CA".organization (str) – Organization (O) field. Default:
"My Company".country (str) – Two-letter ISO 3166-1 alpha-2 country code (C field). Default:
"UA".key_size (int) – RSA key length in bits. Use
2048for standard security or4096for long-lived roots. Default:2048.days_valid (int) – Validity period in calendar days. Default:
3650(≈10 years).valid_from (datetime.datetime | None) – Start of the validity period.
Noneuses the current UTC time.logger (Logger | None) – Optional logger. Falls back to
DEFAULT_LOGGER.
- Returns:
(certificate, private_key)— both must be persisted by the caller.- Return type:
tuple[x509.Certificate, rsa.RSAPrivateKey]
- Raises:
InvalidRangeTimeCertificate – If the computed expiry date is already in the past.
- cosign_certificate(cert, days_valid=None, valid_from=None)[source]¶
Re-sign an existing certificate with this CA’s key and certificate.
Creates a new
x509.Certificatethat preserves the original Subject, public key, and all v3 extensions, but replaces:Issuer — set to this CA’s Subject.
AuthorityKeyIdentifier — updated to reflect this CA’s SKI.
Serial number — a fresh serial is generated so the co-signed certificate is distinguishable from the original in CRLs and logs.
Validity window — optionally overridden via days_valid and valid_from; when both are
Nonethe original window is preserved exactly.
The certificate is signed with SHA-256 using
self._ca.ca_key.Note
This operation does not verify that the original certificate was valid or trusted before co-signing. Call
validate_cert()first if pre-validation is required.- Parameters:
cert (x509.Certificate) – The source certificate whose Subject, public key, and extensions are copied into the co-signed output.
days_valid (int | None) – Override the validity duration in calendar days, counted from valid_from (or
nowwhen valid_from is alsoNone).Nonepreserves the originalnot_valid_before/not_valid_afterwindow unchanged.valid_from (datetime.datetime | None) – Override the start of the validity window. Ignored when days_valid is
None.None+ days_valid set → uses the current UTC time as the start.
- Returns:
A new certificate object identical in content to cert except for the issuer, AKI, serial number, and (optionally) validity window. Must be persisted by the caller.
- Return type:
x509.Certificate
- Raises:
InvalidRangeTimeCertificate – If days_valid is provided and the computed expiry is already in the past.
Examples
>>> cosigned = factory.cosign_certificate(third_party_cert, days_valid=365) >>> assert cosigned.issuer == factory._ca.ca_cert.subject >>> assert cosigned.subject == third_party_cert.subject
- export_pkcs12(cert, private_key, password=None, name=None)[source]¶
Pack cert and private_key into a PKCS#12 (PFX) bundle.
PKCS#12 is the standard container format accepted by Windows certificate stores, macOS Keychain, Java keystores, and most browser import dialogs. The CA certificate is automatically included as the issuer in the chain.
- Parameters:
cert (x509.Certificate) – The leaf certificate to export.
private_key (rsa.RSAPrivateKey) – The private key corresponding to cert’s public key.
password (bytes | None) – Optional password to encrypt the PKCS#12 file.
Noneproduces an unencrypted bundle (not recommended for production).name (str | None) – Friendly name (alias) embedded in the PKCS#12 bag. Defaults to the certificate’s Common Name when
None.
- Returns:
Raw DER-encoded PKCS#12 bytes. Write to a
.p12or.pfxfile, or send as an HTTP response withContent-Type: application/x-pkcs12.- Return type:
- get_cert_chain(cert)[source]¶
Return the full certificate chain from cert up to the CA root.
For a single-level PKI (leaf → root CA) this returns
[cert, ca_cert]. The list is ordered leaf-first, root-last — the same order expected by nginxssl_certificate, envoytls_certificates, and thefullchain.pemconvention used by Let’s Encrypt.- Parameters:
cert (x509.Certificate) – The leaf (or intermediate) certificate to start the chain from.
- Returns:
[cert, self._ca.ca_cert]— leaf first, CA root last.- Return type:
list[x509.Certificate]
- static inspect_certificate(cert)[source]¶
Extract and return a structured, human-readable summary of cert.
Parses every commonly-used X.509 v3 extension and Subject attribute into plain Python values wrapped in a
CertificateDetailsdataclass. The method never performs cryptographic verification — usevalidate_cert()for that. It is therefore safe to call on certificates from any issuer.- Parameters:
cert (x509.Certificate) – The certificate to inspect. May have been issued by this CA or by a completely different PKI.
- Returns:
A frozen dataclass with the following fields populated:
serial_number— raw integer serial.common_name/organization/country— first matching Subject attribute, orNonewhen absent.issuer_cn— CN from the Issuer field, orNone.not_valid_before/not_valid_after— UTC datetimes.is_ca—TruewhenBasicConstraints.caisTrue.san_dns/san_ip— lists from the SAN extension.key_usage— list of enabledKeyUsagebit names.extended_key_usage— list of EKU OID dotted strings.fingerprint_sha256— colon-separated uppercase hex.subject_key_identifier— hex string orNone.public_key_size— RSA key bits orNone.
- Return type:
Examples
>>> details = CertificateFactory.inspect_certificate(cert) >>> print(details.common_name) 'nginx.internal' >>> print(details.is_ca) False >>> print(details.fingerprint_sha256[:8]) 'AB:CD:EF'
- issue_certificate(common_name, serial_type=CertType.SERVICE, key_size=2048, days_valid=365, valid_from=None, email=None, is_server_cert=False, is_client_cert=False, san_dns=None, san_ip=None)[source]¶
Issue a signed end-entity certificate for the given subject parameters.
Workflow: 1. Generate a fresh RSA key pair. 2. Build the Subject
x509.Namefrom CA base info + common_name / email. 3. Create a CSR signed with the new private key. 4. Assemble X.509 extensions (KeyUsage, EKU, SAN, SKI, AKI). 5. Sign the certificate with the CA key fromself._ca.The Subject inherits country and organization from the CA’s own certificate so that all issued certificates share a consistent issuer hierarchy.
- Parameters:
common_name (str) – Common Name (CN) for the new certificate’s Subject.
serial_type (CertType) – Certificate category used when encoding the serial number. Default:
CertType.SERVICE.key_size (int) – RSA key length in bits. Default:
2048.days_valid (int) – Validity period in calendar days. Default:
365.valid_from (datetime.datetime | None) – Start of the validity period.
Noneuses the current UTC time.email (str | None) – Optional email address added as an
emailAddressSubject attribute.is_server_cert (bool) – When
True, addsServerAuthto the Extended Key Usage extension and includes common_name as a DNS SAN (RFC 2818 compliance).is_client_cert (bool) – When
True, addsClientAuthto the Extended Key Usage extension.san_dns (list[str] | None) – Additional DNS names for the Subject Alternative Name extension.
san_ip (list[str] | None) – IP addresses (as strings) for the Subject Alternative Name extension.
- Returns:
(certificate, private_key, csr)— the certificate and key must be persisted by the caller; the CSR is returned for audit purposes.- Return type:
tuple[x509.Certificate, rsa.RSAPrivateKey, x509.CertificateSigningRequest]
- Raises:
InvalidRangeTimeCertificate – If the computed expiry date is already in the past.
- issue_intermediate_ca(common_name, key_size=4096, days_valid=1825, valid_from=None, path_length=0, organization=None, country=None)[source]¶
Issue a subordinate (intermediate) CA certificate signed by this CA.
The resulting certificate has
BasicConstraints(ca=True)andKeyUsage(key_cert_sign=True, crl_sign=True)so it can in turn sign leaf certificates. Thepath_lengthconstraint limits how deep the sub-hierarchy can go.- Parameters:
common_name (str) – CN for the intermediate CA Subject.
key_size (int) – RSA key size for the intermediate CA key. Defaults to
4096(recommended for long-lived CA keys).days_valid (int) – Validity in calendar days. Defaults to
1825(5 years).valid_from (datetime.datetime | None) – Start of the validity window.
Noneuses the current UTC time.path_length (int | None) –
BasicConstraints.path_lengthvalue.0means this intermediate can only sign leaf certificates (cannot create further sub-CAs).Nonemeans unlimited sub-levels.organization (str | None) – O field for the intermediate CA Subject. Falls back to the parent CA’s organization when
None.country (str | None) – C field. Falls back to the parent CA’s country when
None.
- Returns:
(intermediate_ca_cert, intermediate_ca_key).- Return type:
tuple[x509.Certificate, rsa.RSAPrivateKey]
- Raises:
InvalidRangeTimeCertificate – If the computed expiry is already in the past.
- renew_certificate(cert, days_valid=365, valid_from=None)[source]¶
Issue a renewal of cert with a fresh validity window but the same Subject, public key, and extensions.
Unlike
rotate_certificate()(which generates a new key pair), renewal re-uses the existing public key. This is appropriate when the private key has not been compromised and the owner simply needs to extend the validity period.The renewed certificate receives a new serial number generated by
SerialWithEncodingso it is distinguishable from the original in CRLs and audit logs.- Parameters:
cert (x509.Certificate) – The certificate to renew. Its Subject, public key, and all v3 extensions (except AKI, which is updated to point to the current CA) are copied verbatim into the renewal.
days_valid (int) – Number of days the renewed certificate should be valid. Default:
365.valid_from (datetime.datetime | None) – Start of the new validity window.
Noneuses the current UTC time.
- Returns:
A freshly signed certificate with the same identity but a new validity window and serial number.
- Return type:
x509.Certificate
- Raises:
InvalidRangeTimeCertificate – If the computed expiry is already in the past.
- validate_cert(cert)[source]¶
Verify that cert was issued by this CA, is within its validity window, and carries a cryptographically correct signature.
Three checks are performed in order: 1. Issuer match —
cert.issuermust equal the CA’s Subject. 2. Validity window — current UTC time must be betweencert.not_valid_before_utcandcert.not_valid_after_utc.Signature — the CA public key is used to verify the certificate signature using PKCS#1 v1.5 with the algorithm declared in the cert.
- Parameters:
cert (x509.Certificate) – The certificate to validate.
- Returns:
Returns silently when all checks pass.
- Return type:
None
- Raises:
ValidationCertError – If any of the three checks fails. The message describes which check failed and includes the relevant values (timestamps, issuer).
- verify_crl(crl)[source]¶
Verify the signature and validity window of crl.
Checks that: 1. The CRL was signed by this CA’s private key (issuer match + signature). 2. The CRL’s
nextUpdatetimestamp has not yet passed — i.e. the CRLis still within its declared validity window.
- Parameters:
crl (x509.CertificateRevocationList) – The CRL object to verify.
- Returns:
Returns silently when all checks pass.
- Return type:
None
- Raises:
ValidationCertError – If the CRL issuer does not match this CA, the signature is invalid, or the CRL has expired (
nextUpdateis in the past).
- Parameters:
ca_loader (ICALoader)
logger (Logger | None)