"""PKCS#12 client-certificate records and checks for AEAT Sede Electrónica.
This module is the public surface for loading and evaluating certificates used
for authentication against the Spanish tax authority's Sede Electrónica.
:class:`adapters.outbound.aeat.auth.authenticator.AeatAuthenticator` consumes this
surface by loading a :class:`CertificateBundle` into a :class:`LoadedCertificate`,
recording :class:`CertificateHealth`, and deriving the taxpayer NIF/NIE through
:func:`extract_nif_from_subject`.
Design constraints:
* All boundary records are pydantic v2 ``BaseModel`` with
``model_config`` set to the shared strict, frozen project config.
* Cert passphrases are :class:`pydantic.SecretStr`. The secret value is
materialised only at the PKCS#12 or Playwright context boundary and is never
logged, persisted, or serialised by ``model_dump``.
* Parsed private-key material and the raw PKCS#12 bytes live in
:class:`pydantic.PrivateAttr` fields on :class:`LoadedCertificate`,
so they can never be leaked via ``model_dump`` or ``repr``.
* All errors inherit from :class:`core.errors.hierarchy.CadrumoError` via
:class:`CertificateError`.
See Also:
:class:`adapters.outbound.aeat.auth.providers.CertificateContextProvisioner`
for wiring :class:`LoadedCertificate` into browser contexts.
"""
from __future__ import annotations
import re
from datetime import UTC, datetime
from enum import StrEnum
from pathlib import Path
from typing import override
from cryptography import x509
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.serialization import pkcs12
from cryptography.x509.oid import NameOID
from pydantic import BaseModel, Field, PrivateAttr, SecretStr
from .....application.auth.operator_probe_ports import (
CertificateHealthBand,
CertificateHealthFailure,
CertificateHealthObservation,
CertificateHealthProbePort,
CertificateHealthProbeRequest,
CertificateHealthProbeResult,
)
from .....core.errors.hierarchy import AuthError
from .....core.external_constants import UTF_8_ENCODING
from .....core.identity.documents import IdentityDocument, IdentityError
from .....core.logging import get_logger
from .....core.models import STRICT_FROZEN_CONFIG
from .....core.time.utc import coerce_utc_aware
from .....domain.calculations.registry.tax_id_runtime import (
validate_runtime_identity,
validate_runtime_spanish_tax_id,
)
from .errors import AuthValidationError
log = get_logger(__name__)
# ── Errors ──────────────────────────────────────────────────────────────────
[docs]
class CertificateError(AuthError):
"""Base class for every certificate-auth domain error.
Subclasses remain catchable through the shared :class:`AuthError` branch
while preserving certificate-specific causes for loading, password,
health, and subject-identity failures.
"""
[docs]
class CertificateLoadError(CertificateError):
"""Raised when :func:`load_certificate` cannot parse PKCS#12 bytes."""
[docs]
class CertificatePasswordError(CertificateError):
"""Raised when :class:`CertificateBundle.password` is empty or wrong."""
[docs]
class CertificateExpiredError(CertificateError):
"""Raised when :func:`load_certificate` sees an elapsed ``not_after``."""
[docs]
class CertificatePreExpiryError(CertificateError):
"""Raised when a certificate is within the pre-expiry danger window.
Distinct from :class:`CertificateExpiredError` (which fires after
``not_after`` has elapsed): this error is raised proactively by the
workflow gate and CLI surfaces when a loaded certificate's
``days_until_expiry`` has fallen below the configured critical
threshold, before the bundle becomes technically unusable. Callers
may suppress it via an explicit override flag on the narrow
programmatic surfaces that still support certificate probes.
"""
[docs]
class CertificateNifParseError(CertificateError):
"""Raised when no NIF / NIE can be parsed from a certificate subject.
The project's authenticator derives the taxpayer NIF from the
FNMT certificate subject (canonical source: the ``serialNumber``
RDN, OID 2.5.4.5). Certificates that carry no such attribute,
that use a CIF (legal-entity) shape, or whose CN/serialNumber
lacks a registry-recognised persona-física identifier produce this
error. Callers MUST propagate it rather than guess the identifier from
other fields.
"""
# AeatLoginAssertionError and AeatSessionExpiredError are now in .errors
# ── Enums ───────────────────────────────────────────────────────────────────
[docs]
class CertificateHealthSeverity(StrEnum):
"""Closed catalogue of certificate health verdicts.
Mapping from ``days_until_expiry`` to severity is driven by the
``warn_threshold_days`` / ``critical_threshold_days`` fields on the
:class:`CertificateHealth` record and the sourced values in
:class:`core.config.Settings`.
Attributes:
OK: Certificate has more than ``warn_threshold_days`` remaining.
WARN: Within the warning window but outside the critical one.
CRITICAL: Inside the critical window but not yet expired.
EXPIRED: ``not_after`` has already elapsed.
"""
OK = "OK"
WARN = "WARN"
CRITICAL = "CRITICAL"
EXPIRED = "EXPIRED"
# ── Pydantic boundary records ───────────────────────────────────────────────
[docs]
class CertificateBundle(BaseModel):
"""Operator-supplied pointer at a PKCS#12 bundle on disk.
:func:`load_certificate` turns this pointer into a
:class:`LoadedCertificate`.
The PKCS#12 passphrase is carried directly as a
:class:`pydantic.SecretStr` so callers no longer have to round-trip
the secret through ``os.environ``. The secret is materialised
only at the exact PKCS#12-decode boundary and is never logged,
persisted, or serialised by ``model_dump``.
Attributes:
path: Filesystem path to the ``.p12`` / ``.pfx`` bundle.
password: PKCS#12 passphrase as a :class:`SecretStr`.
friendly_name: Optional human-readable label for logs.
"""
model_config = STRICT_FROZEN_CONFIG
path: Path
password: SecretStr
friendly_name: str | None = None
[docs]
class LoadedCertificate(BaseModel):
"""A parsed, validated, in-memory PKCS#12 certificate.
:class:`adapters.outbound.aeat.auth.authenticator.AeatAuthenticator` uses this
record for NIF/NIE extraction, :class:`CertificateHealth` evaluation,
and browser-context provisioning.
Public fields are safe to log and serialise. Secret material
(raw PKCS#12 bytes, parsed private key, passphrase) lives in
:class:`pydantic.PrivateAttr` fields and is therefore invisible to
``model_dump``, ``model_dump_json``, and the overridden
:meth:`__repr__`.
Attributes:
subject: X.509 subject distinguished name.
issuer: X.509 issuer distinguished name.
not_before: Validity start (timezone-aware UTC).
not_after: Validity end (timezone-aware UTC).
serial_number: Hex-encoded serial number.
sha256_thumbprint: Hex-encoded SHA-256 fingerprint of the DER
encoding.
source_path: Path the bundle was loaded from.
friendly_name: Optional label propagated from the bundle.
"""
model_config = STRICT_FROZEN_CONFIG
subject: str
issuer: str
not_before: datetime
not_after: datetime
serial_number: str
sha256_thumbprint: str
source_path: Path
friendly_name: str | None
_pkcs12_bytes: bytes = PrivateAttr(default=b"")
_password: SecretStr = PrivateAttr(default=SecretStr(""))
_private_key_handle: object | None = PrivateAttr(default=None)
[docs]
def client_certificate_material(self) -> tuple[bytes, str]:
"""Return the PKCS#12 bytes and passphrase for a client-TLS handshake.
The bundle and its passphrase are held as private attributes so they
never reach a model dump, log line, or serialized payload. This is the
one authorized read: the browser context binds them to the AEAT origin
for mutual TLS and nothing else. Callers must not store or forward the
returned material.
"""
return self._pkcs12_bytes, self._password.get_secret_value()
[docs]
def is_expired(self, now: datetime | None = None) -> bool:
"""Return True if the certificate's validity has elapsed.
Args:
now: Timezone-aware reference time. Defaults to
:func:`datetime.now` in UTC.
Returns:
True when the certificate has expired relative to ``now``.
"""
reference = now if now is not None else datetime.now(UTC)
return reference > self.not_after
@override
def __repr__(self) -> str: # pragma: no cover - trivial
"""Return a developer-readable subject, issuer, and thumbprint summary."""
return (
f"LoadedCertificate(subject={self.subject!r}, "
f"issuer={self.issuer!r}, "
f"sha256_thumbprint={self.sha256_thumbprint!r})"
)
[docs]
class CertificateHealth(BaseModel):
"""Structured health verdict for a PKCS#12 certificate bundle.
Computed from a loaded certificate's ``not_after`` against a
reference ``evaluated_at`` timestamp and a pair of warning /
critical thresholds sourced from
:class:`core.config.Settings`. The record never carries any
secret material; it is safe to log, persist, or surface to the
CLI. :func:`evaluate_loaded_certificate_health` computes this from an
existing :class:`LoadedCertificate`; :func:`health` computes it from a
bundle path while preserving the expired-certificate reporting path.
Attributes:
subject: RFC-4514 subject DN.
issuer: RFC-4514 issuer DN.
serial_number: Hex-encoded serial number.
not_before: Timezone-aware validity start.
not_after: Timezone-aware validity end.
days_until_expiry: Whole days between ``evaluated_at`` and
``not_after``. Negative when the certificate is expired.
severity: :class:`CertificateHealthSeverity` bucket.
warn_threshold_days: The WARN cut-off that produced this
verdict.
critical_threshold_days: The CRITICAL cut-off that produced
this verdict.
evaluated_at: Timezone-aware reference timestamp.
"""
model_config = STRICT_FROZEN_CONFIG
subject: str
issuer: str
serial_number: str
not_before: datetime
not_after: datetime
days_until_expiry: int
severity: CertificateHealthSeverity
warn_threshold_days: int = Field(gt=0)
critical_threshold_days: int = Field(gt=0)
evaluated_at: datetime
# ── Loader ──────────────────────────────────────────────────────────────────
[docs]
def load_certificate(bundle: CertificateBundle) -> LoadedCertificate:
"""Load and validate a PKCS#12 bundle from disk.
This is the canonical decode path for certificate auth. It feeds
:class:`adapters.outbound.aeat.auth.authenticator.AeatAuthenticator`, operator
probes, and backend provisioning surfaces with the same
:class:`LoadedCertificate` contract.
The passphrase is unwrapped from ``bundle.password`` at the
PKCS#12-decode boundary only. An empty :class:`SecretStr` raises
:class:`CertificatePasswordError` *before* any file I/O. On a
successful load the returned :class:`LoadedCertificate` carries the
raw PKCS#12 bytes and a parsed private-key handle in
:class:`PrivateAttr` fields so the backends can consume them
without a second on-disk round-trip.
Args:
bundle: Operator-supplied :class:`CertificateBundle`.
Returns:
A frozen :class:`LoadedCertificate`. Its public fields are safe
to log; secret material is never serialised.
Raises:
CertificatePasswordError: Empty passphrase or wrong passphrase.
CertificateLoadError: PKCS#12 bytes cannot be parsed.
CertificateExpiredError: Certificate's validity has elapsed.
"""
raw_password = _require_certificate_password(bundle)
raw_bytes = _read_pkcs12_bytes(bundle)
parsed = _decode_pkcs12(bundle, raw_bytes, raw_password)
parsed_cert = _require_end_entity_certificate(parsed, bundle)
loaded = _build_loaded_certificate(
bundle,
parsed_cert.certificate,
friendly_name=_resolve_friendly_name(bundle.friendly_name, parsed_cert.friendly_name),
)
_attach_private_material(loaded, raw_bytes=raw_bytes, bundle=bundle, private_key=parsed.key)
_raise_if_expired(loaded)
log.info("loaded PKCS#12 certificate: thumbprint=%s", loaded.sha256_thumbprint)
return loaded
def _require_certificate_password(bundle: CertificateBundle) -> str:
"""Materialise and validate the passphrase immediately before loading."""
raw_password = bundle.password.get_secret_value()
if not raw_password:
raise CertificatePasswordError(
f"passphrase for PKCS#12 bundle at {bundle.path} is empty; "
"construct CertificateBundle with a non-empty SecretStr password.",
)
return raw_password
def _read_pkcs12_bytes(bundle: CertificateBundle) -> bytes:
"""Read the bundle bytes, translating filesystem failures."""
try:
return bundle.path.read_bytes()
except OSError as exc:
raise CertificateLoadError(f"could not read PKCS#12 bundle at {bundle.path}: {exc}") from exc
def _decode_pkcs12(
bundle: CertificateBundle,
raw_bytes: bytes,
raw_password: str,
) -> pkcs12.PKCS12KeyAndCertificates:
"""Decode PKCS#12 bytes and preserve password/load error classification."""
try:
return pkcs12.load_pkcs12(raw_bytes, raw_password.encode(UTF_8_ENCODING))
except ValueError as exc:
message = str(exc).lower()
if "invalid password" in message or "mac verify" in message:
raise CertificatePasswordError(
f"wrong passphrase for PKCS#12 bundle at {bundle.path}",
) from exc
raise CertificateLoadError(f"could not parse PKCS#12 bundle at {bundle.path}: malformed bytes") from exc
def _require_end_entity_certificate(
parsed: pkcs12.PKCS12KeyAndCertificates,
bundle: CertificateBundle,
) -> pkcs12.PKCS12Certificate:
"""Require the end-entity certificate returned by the PKCS#12 decoder."""
if parsed.cert is None:
raise CertificateLoadError(f"PKCS#12 bundle at {bundle.path} contains no end-entity certificate")
return parsed.cert
def _resolve_friendly_name(explicit_name: str | None, encoded_name: bytes | None) -> str | None:
"""Prefer the operator label and decode the bundle label when available."""
if explicit_name is None and encoded_name is not None:
try:
return encoded_name.decode(UTF_8_ENCODING)
except UnicodeDecodeError:
return None
return explicit_name
def _build_loaded_certificate(
bundle: CertificateBundle,
x509_cert: x509.Certificate,
*,
friendly_name: str | None,
) -> LoadedCertificate:
"""Project decoded X.509 metadata into the safe public record."""
return LoadedCertificate(
subject=x509_cert.subject.rfc4514_string(),
issuer=x509_cert.issuer.rfc4514_string(),
not_before=coerce_utc_aware(x509_cert.not_valid_before_utc),
not_after=coerce_utc_aware(x509_cert.not_valid_after_utc),
serial_number=format(x509_cert.serial_number, "x"),
sha256_thumbprint=x509_cert.fingerprint(hashes.SHA256()).hex(),
source_path=bundle.path,
friendly_name=friendly_name,
)
def _attach_private_material(
loaded: LoadedCertificate,
*,
raw_bytes: bytes,
bundle: CertificateBundle,
private_key: object | None,
) -> None:
"""Attach decoded private material only to the model's private fields."""
object.__setattr__(loaded, "_pkcs12_bytes", raw_bytes)
object.__setattr__(loaded, "_password", bundle.password)
object.__setattr__(loaded, "_private_key_handle", private_key)
def _raise_if_expired(loaded: LoadedCertificate) -> None:
"""Refuse an expired certificate after metadata and private material load."""
if loaded.is_expired():
raise CertificateExpiredError(
f"certificate for subject {loaded.subject!r} expired at {loaded.not_after.isoformat()}",
)
# ── Pre-expiry health evaluator ─────────────────────────────────────────────
def _bucket_severity(
*,
days_until_expiry: int,
warn_days: int,
critical_days: int,
) -> CertificateHealthSeverity:
"""Map ``days_until_expiry`` to a :class:`CertificateHealthSeverity`.
Boundary semantics: a cert with exactly ``critical_days`` remaining
is classified CRITICAL (inclusive), and exactly ``warn_days``
remaining is classified WARN (inclusive). Negative / zero days
(i.e. expired) always produce EXPIRED.
Args:
days_until_expiry: Whole days between ``evaluated_at`` and
``not_after``.
warn_days: Warning window in days (must be ``> critical_days``).
critical_days: Critical window in days.
Returns:
The appropriate severity bucket.
"""
if days_until_expiry <= 0:
return CertificateHealthSeverity.EXPIRED
if days_until_expiry <= critical_days:
return CertificateHealthSeverity.CRITICAL
if days_until_expiry <= warn_days:
return CertificateHealthSeverity.WARN
return CertificateHealthSeverity.OK
[docs]
def evaluate_loaded_certificate_health(
cert: LoadedCertificate,
*,
warn_days: int,
critical_days: int,
now: datetime | None = None,
) -> CertificateHealth:
"""Compute a :class:`CertificateHealth` from an already-loaded cert.
The helper exists so callers that have already paid the PKCS#12
decode cost, such as :class:`adapters.outbound.aeat.auth.authenticator.AeatAuthenticator`
or operator probes, can reuse the parsed record rather than re-reading the
bundle from disk.
Args:
cert: A previously-loaded :class:`LoadedCertificate`.
warn_days: Warning threshold in days. Must be > ``critical_days``.
critical_days: Critical threshold in days. Must be positive.
now: Optional timezone-aware reference timestamp. Defaults to
:func:`datetime.now` in UTC.
Returns:
A frozen :class:`CertificateHealth` record.
Raises:
AuthValidationError: If ``critical_days <= 0`` or ``warn_days <= critical_days``.
"""
if critical_days <= 0:
raise AuthValidationError(f"critical_days must be positive, got {critical_days}")
if warn_days <= critical_days:
raise AuthValidationError(
f"warn_days ({warn_days}) must be strictly greater than critical_days ({critical_days})",
)
evaluated_at = coerce_utc_aware(now) if now is not None else datetime.now(UTC)
delta_seconds = (cert.not_after - evaluated_at).total_seconds()
# Floor division keeps "one second before expiry" at 0 days → EXPIRED.
days_until_expiry = int(delta_seconds // 86400)
severity = _bucket_severity(
days_until_expiry=days_until_expiry,
warn_days=warn_days,
critical_days=critical_days,
)
return CertificateHealth(
subject=cert.subject,
issuer=cert.issuer,
serial_number=cert.serial_number,
not_before=cert.not_before,
not_after=cert.not_after,
days_until_expiry=days_until_expiry,
severity=severity,
warn_threshold_days=warn_days,
critical_threshold_days=critical_days,
evaluated_at=evaluated_at,
)
[docs]
def health(
path: Path,
*,
password: SecretStr,
warn_days: int,
critical_days: int,
friendly_name: str | None = None,
now: datetime | None = None,
) -> CertificateHealth:
"""Load ``path`` and return its :class:`CertificateHealth`.
Unlike :func:`load_certificate`, this function **never** raises on
an expired certificate — it returns a
:class:`CertificateHealth` record with severity
:attr:`CertificateHealthSeverity.EXPIRED` instead. Genuine load
failures (empty passphrase, corrupt bytes, I/O) still raise the
matching :class:`CertificateError` subclass, because those are not
pre-expiry conditions.
Args:
path: Filesystem path to the PKCS#12 bundle.
password: PKCS#12 passphrase as a :class:`SecretStr`.
warn_days: Warning threshold in days (see
:func:`evaluate_loaded_certificate_health`).
critical_days: Critical threshold in days.
friendly_name: Optional label propagated to the bundle.
now: Optional reference time, for deterministic tests.
Returns:
A frozen :class:`CertificateHealth` record.
Raises:
CertificateExpiredError: When the certificate has expired and the raw bytes
cannot be re-decoded for the health report.
CertificateLoadError: When the PKCS#12 bytes cannot be re-decoded for an
expired-cert health report.
"""
bundle = CertificateBundle(
path=path,
password=password,
friendly_name=friendly_name,
)
try:
loaded = load_certificate(bundle)
except CertificateExpiredError:
# Re-load the raw bytes just to extract the metadata for the
# health record. load_certificate refuses to return the
# LoadedCertificate once expiry is detected, so we repeat the
# minimal x509 decode here. A second decode failure is
# surfaced as CertificateLoadError rather than swallowed, to
# honour the "never-crash on pre-expiry path" contract.
try:
raw_bytes = path.read_bytes()
parsed = pkcs12.load_pkcs12(raw_bytes, password.get_secret_value().encode(UTF_8_ENCODING))
except (OSError, ValueError) as exc:
raise CertificateLoadError(
f"could not re-decode PKCS#12 bundle at {path} for expired-cert health report: {exc}",
) from exc
if parsed.cert is None: # pragma: no cover - defended above
raise
x509_cert = parsed.cert.certificate
not_before = coerce_utc_aware(x509_cert.not_valid_before_utc)
not_after = coerce_utc_aware(x509_cert.not_valid_after_utc)
evaluated_at = coerce_utc_aware(now) if now is not None else datetime.now(UTC)
delta_seconds = (not_after - evaluated_at).total_seconds()
days_until_expiry = int(delta_seconds // 86400)
return CertificateHealth(
subject=x509_cert.subject.rfc4514_string(),
issuer=x509_cert.issuer.rfc4514_string(),
serial_number=format(x509_cert.serial_number, "x"),
not_before=not_before,
not_after=not_after,
days_until_expiry=days_until_expiry,
severity=CertificateHealthSeverity.EXPIRED,
warn_threshold_days=warn_days,
critical_threshold_days=critical_days,
evaluated_at=evaluated_at,
)
return evaluate_loaded_certificate_health(
loaded,
warn_days=warn_days,
critical_days=critical_days,
now=now,
)
[docs]
class CertificateHealthProbeAdapter(CertificateHealthProbePort):
"""Translate certificate adapter health records for the application probe."""
[docs]
@override
def evaluate(self, request: CertificateHealthProbeRequest) -> CertificateHealthProbeResult:
"""Return application-owned health facts, translating load failures."""
try:
result = health(
request.path,
password=request.password,
warn_days=request.warn_days,
critical_days=request.critical_days,
friendly_name=request.friendly_name,
)
except CertificateError as exc:
return CertificateHealthFailure(detail=str(exc))
return CertificateHealthObservation(
severity=CertificateHealthBand(result.severity.value.lower()),
days_until_expiry=result.days_until_expiry,
)
# ── NIF / NIE extraction from FNMT subject ──────────────────────────────────
_SERIAL_PREFIX_RE = re.compile(r"^IDCES-", re.IGNORECASE)
def _normalise_candidate(candidate: str) -> str:
"""Strip surrounding whitespace and the optional ``IDCES-`` prefix."""
stripped = candidate.strip()
return _SERIAL_PREFIX_RE.sub("", stripped).upper()
def _persona_fisica_identifier(candidate: str) -> str | None:
"""Return the canonical registry-recognised persona-física identifier.
A DNI written without its leading zero is zero-padded to the canonical
width before :func:`validate_runtime_identity` resolves its document family and
:func:`~cadrumo.domain.calculations.registry.tax_id_runtime.validate_runtime_spanish_tax_id`
verifies it. A
checksum-invalid or non-persona candidate is skipped so the caller can
continue to the next subject attribute.
"""
normalised = candidate.strip().upper()
for candidate_form in (normalised, "0" + normalised):
try:
identity_kind = validate_runtime_identity(candidate_form)
except IdentityError:
continue
if identity_kind not in (IdentityDocument.NIF, IdentityDocument.NIE):
continue
try:
return validate_runtime_spanish_tax_id(candidate_form)
except IdentityError:
continue
return None
def _trailing_persona_fisica_identifier(value: str) -> str | None:
"""Resolve an identifier at the end of a common-name value.
FNMT common names may contain a human-readable name, separators, and an
identifier. The terminal alphanumeric run is searched through its
suffixes so the historical extraction contract remains intact for values
such as ``NAME - identifier`` and ``IDCES-identifier``. Every candidate
is classified by :func:`validate_runtime_identity`; this adapter declares no
identifier shape.
"""
stripped = value.rstrip()
if not stripped or not stripped[-1].isalnum():
return None
start = len(stripped)
while start and stripped[start - 1].isalnum():
start -= 1
terminal = stripped[start:].upper()
for candidate_start in range(len(terminal)):
identifier = _persona_fisica_identifier(terminal[candidate_start:])
if identifier is not None:
return identifier
return None
def _iter_rdn_values(subject: str, oid: x509.ObjectIdentifier) -> list[str]:
r"""Return every attribute value in ``subject`` matching ``oid``.
Parses the subject via
:meth:`cryptography.x509.Name.from_rfc4514_string`, which handles
RFC 4514 escape sequences (``\\,``, ``\\+``, ``\\"``, ``\\#``) and
multi-valued RDNs correctly — regex cannot. Returns the raw
attribute values with no normalisation; callers strip prefixes
and validate shape.
"""
try:
name = x509.Name.from_rfc4514_string(subject)
except ValueError:
return []
return [attribute.value for attribute in name.get_attributes_for_oid(oid) if isinstance(attribute.value, str)]
__all__ = [
"CertificateBundle",
"CertificateError",
"CertificateExpiredError",
"CertificateHealth",
"CertificateHealthProbeAdapter",
"CertificateHealthSeverity",
"CertificateLoadError",
"CertificateNifParseError",
"CertificatePasswordError",
"CertificatePreExpiryError",
"LoadedCertificate",
"evaluate_loaded_certificate_health",
"extract_nif_from_subject",
"health",
"load_certificate",
]