<!-- Copyright (c) 2026 defconxt. All rights reserved. -->
<!-- Licensed under AGPL-3.0 — see LICENSE file for details. -->
---
name: automating-certificate-management
description: >-
  Automate TLS/SSL certificate lifecycle management including discovery,
  expiration monitoring, renewal orchestration via ACME/Let's Encrypt,
  certificate transparency log monitoring, and compliance auditing for
  weak ciphers and deprecated protocols.
domain: cybersecurity
subdomain: automation-scripting
tags:
  - certificate-management
  - tls-ssl
  - acme
  - lets-encrypt
  - certificate-transparency
  - pki
version: "1.0"
author: defconxt
license: AGPL-3.0
metadata:
  mitre-attack: ["T1588.004"]
---

# Automating Certificate Management

## Overview

Expired or misconfigured certificates cause outages and security gaps. This
skill covers building automated pipelines that discover certificates across
infrastructure, monitor expiration, trigger ACME renewals, and audit for
weak configurations (SHA-1, short keys, deprecated TLS versions).

Mode: `[MODE: BLUE]` — Defensive PKI operations with compliance focus.

## Prerequisites

| Requirement | Details |
|---|---|
| Python 3.10+ with `cryptography`, `pyOpenSSL` | Required |
| Network access to target hosts on port 443 (or custom TLS ports) | Required |
| ACME client access for automated renewal (certbot or acme.sh) | Required |

## Key Concepts

### Certificate Discovery and Inspection

```python
import ssl
import socket
from datetime import datetime, timezone
from cryptography import x509
from cryptography.hazmat.backends import default_backend

def inspect_certificate(host: str, port: int = 443) -> dict:
    """Connect to host and extract certificate details."""
    ctx = ssl.create_default_context()
    ctx.check_hostname = False
    ctx.verify_mode = ssl.CERT_NONE
    with socket.create_connection((host, port), timeout=10) as sock:
        with ctx.wrap_socket(sock, server_hostname=host) as ssock:
            der = ssock.getpeercert(binary_form=True)
    cert = x509.load_der_x509_certificate(der, default_backend())
    now = datetime.now(timezone.utc)
    return {
        "host": host,
        "port": port,
        "subject": cert.subject.rfc4514_string(),
        "issuer": cert.issuer.rfc4514_string(),
        "not_before": cert.not_valid_before_utc.isoformat(),
        "not_after": cert.not_valid_after_utc.isoformat(),
        "days_remaining": (cert.not_valid_after_utc - now).days,
        "serial": str(cert.serial_number),
        "key_size": cert.public_key().key_size,
        "signature_algorithm": cert.signature_algorithm_oid._name,
    }
```

### Expiration Monitor

```python
def check_expiration(hosts: list[tuple[str, int]], warn_days: int = 30) -> list[dict]:
    """Check multiple hosts and flag certificates expiring soon."""
    results = []
    for host, port in hosts:
        try:
            info = inspect_certificate(host, port)
            info["status"] = (
                "expired" if info["days_remaining"] < 0
                else "warning" if info["days_remaining"] < warn_days
                else "ok"
            )
            results.append(info)
        except Exception as e:
            results.append({"host": host, "port": port, "status": "error", "detail": str(e)})
    return results
```

### Weak Configuration Audit

```python
def audit_tls_config(host: str, port: int = 443) -> dict:
    """Check for weak TLS configurations."""
    findings = []
    info = inspect_certificate(host, port)
    if info["key_size"] < 2048:
        findings.append({"issue": "weak_key", "detail": f"Key size {info['key_size']} < 2048"})
    if "sha1" in info["signature_algorithm"].lower():
        findings.append({"issue": "sha1_signature", "detail": "SHA-1 signatures deprecated"})
    # Test for TLS 1.0/1.1
    for proto in [ssl.TLSVersion.TLSv1, ssl.TLSVersion.TLSv1_1]:
        ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
        ctx.minimum_version = proto
        ctx.maximum_version = proto
        ctx.check_hostname = False
        ctx.verify_mode = ssl.CERT_NONE
        try:
            with socket.create_connection((host, port), timeout=5) as s:
                with ctx.wrap_socket(s, server_hostname=host):
                    findings.append({"issue": "deprecated_tls", "detail": f"{proto.name} supported"})
        except (ssl.SSLError, OSError):
            pass
    return {"host": host, "findings": findings, "compliant": len(findings) == 0}
```

## Workflow

### Step 1: Scan Certificates Across Hosts

```bash
node scripts/agent.js --action scan --targets hosts.txt --warn-days 30
```

### Step 2: Audit TLS Configuration

```bash
node scripts/agent.js --action audit --target example.com --port 443
```

### Step 3: Check Single Certificate

```bash
node scripts/agent.js --action inspect --target example.com
```

## Verification

- [ ] Certificate inspector extracts subject, issuer, dates, key size, and algorithm
- [ ] Expiration monitor flags certs within warning threshold
- [ ] TLS audit detects deprecated protocols and weak keys
- [ ] Handles connection timeouts and unreachable hosts
- [ ] Exit code 0 on compliant, 1 on findings, 2 on error

## References

- [Python cryptography Library](https://cryptography.io/)
- [Certificate Transparency](https://certificate.transparency.dev/)
- [Let's Encrypt ACME Protocol](https://letsencrypt.org/docs/)
- [NIST SP 800-52 Rev. 2 — TLS Guidelines](https://csrc.nist.gov/publications/detail/sp/800-52/rev-2/final)
