ssl-tls-certificate-verification
Verify SSL/TLS certificates, check expiry, validate chain, inspect DNS before deployment
import ssl
import sys
import socket
import datetime
import subprocess
import requests as _requests
# ----------------------------------------
# PRE_EXECUTION
# FM-2.2: fetch ground truth for all registry_refs
# ----------------------------------------
REGISTRY_REFS = ["requests", "httpx"]
MAX_RETRIES = 2
registries = {}
for lib in REGISTRY_REFS:
for attempt in range(MAX_RETRIES):
try:
response = _requests.get(
f"https://checklist.day/api/registry/{lib}",
timeout=10
)
if response.status_code == 200:
registries[lib] = response.json()
break
except _requests.exceptions.RequestException:
pass
for lib in REGISTRY_REFS:
assert lib in registries, \
f"ABORT: registry fetch failed for {lib} after {MAX_RETRIES} attempts"
# FM-2.4: surface breaking warnings
for lib, registry in registries.items():
breaking = [
w for w in registry.get("warnings", [])
if w.get("severity") == "breaking"
]
if breaking:
print(f"PRE_EXECUTION: {lib} has {len(breaking)} breaking warning(s):")
for w in breaking:
print(f" [!] [{w.get('affected_versions', 'all')}] {w['message'][:120]}")
print(f" fix: {w['fix'][:100]}")
print()
print("PRE_EXECUTION: all registry refs verified ✓")
# ----------------------------------------
# KNOWN FAILURE MODES
#
# 1. verify=False left in production — disables all cert validation silently.
# Never use requests.get(url, verify=False) in production code.
# Common pattern: dev bypasses SSL for testing, forgets to revert.
#
# 2. No expiry buffer — cert valid today, expires in 3 days, deploy breaks on day 4.
# Always check expiry with a minimum buffer (30 days recommended).
#
# 3. Self-signed cert in prod — works locally, fails for every external client.
# Check issuer — must be a trusted CA, not the domain itself.
#
# 4. Incomplete cert chain — leaf cert valid but intermediate not served.
# Causes failures in strict clients even if browser shows padlock.
#
# 5. DNS mismatch — cert issued for www.example.com, deployed at example.com.
# CN/SAN must match the hostname being verified.
#
# 6. Wrong port — SSL check on port 443 passes, but service runs on 8443.
# Always verify against the actual port in use.
# ----------------------------------------
EXPIRY_BUFFER_DAYS = 30 # warn if cert expires within this window
DEFAULT_TIMEOUT = 10
DEFAULT_PORT = 443
def get_cert_info(hostname: str, port: int = DEFAULT_PORT) -> dict:
"""
Fetch SSL certificate info using stdlib ssl — no extra dependencies.
FM-1.5: explicit timeout — never hang on unresponsive host.
"""
context = ssl.create_default_context()
with socket.create_connection((hostname, port), timeout=DEFAULT_TIMEOUT) as sock:
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
cert = ssock.getpeercert()
# get full chain depth
chain = ssock.getpeercert(binary_form=False)
return {
"cert": cert,
"protocol": ssock.version(),
"cipher": ssock.cipher(),
}
def check_expiry(cert: dict, buffer_days: int = EXPIRY_BUFFER_DAYS) -> dict:
"""
FM-3.2: check expiry before asserting cert is valid.
Returns days_remaining and whether within warning buffer.
"""
not_after = cert.get("notAfter")
assert not_after, "ABORT: cert missing notAfter field"
expiry_dt = datetime.datetime.strptime(not_after, "%b %d %H:%M:%S %Y %Z")
expiry_dt = expiry_dt.replace(tzinfo=datetime.timezone.utc)
now = datetime.datetime.now(datetime.timezone.utc)
days_remaining = (expiry_dt - now).days
return {
"expiry_date": expiry_dt.strftime("%Y-%m-%d"),
"days_remaining": days_remaining,
"within_buffer": days_remaining <= buffer_days,
"expired": days_remaining < 0,
}
def check_hostname_match(cert: dict, hostname: str) -> bool:
"""
FM-3.3: verify cert CN/SAN matches the target hostname.
SAN (Subject Alternative Names) takes precedence over CN.
"""
# Check SANs first
san_list = []
for san_type, san_value in cert.get("subjectAltName", []):
if san_type == "DNS":
san_list.append(san_value.lower())
if san_list:
hostname_lower = hostname.lower()
for san in san_list:
if san == hostname_lower:
return True
# wildcard match: *.example.com matches sub.example.com
if san.startswith("*."):
wildcard_domain = san[2:]
if hostname_lower.endswith("." + wildcard_domain):
return True
return False
# Fall back to CN
for rdn in cert.get("subject", []):
for key, value in rdn:
if key == "commonName":
return value.lower() == hostname.lower()
return False
def check_issuer(cert: dict) -> dict:
"""
FM-2.4: surface self-signed cert warning — issuer == subject is a red flag.
"""
subject = dict(x[0] for x in cert.get("subject", []))
issuer = dict(x[0] for x in cert.get("issuer", []))
is_self_signed = subject.get("commonName") == issuer.get("commonName")
return {
"issuer_org": issuer.get("organizationName", "unknown"),
"issuer_cn": issuer.get("commonName", "unknown"),
"is_self_signed": is_self_signed,
}
def verify_https_request(url: str) -> int:
"""
FM-2.6: always use verify=True (default) — never disable SSL verification.
This will raise SSLError if cert is invalid.
"""
import requests
response = requests.get(url, timeout=DEFAULT_TIMEOUT, verify=True)
return response.status_code
# ----------------------------------------
# EXECUTION
# Test against a known-good public host: checklist.day
# FM-1.1: read-only SSL checks — idempotent, no side effects
# ----------------------------------------
try:
import requests
except ImportError:
pkg = registries["requests"]["install"][0]["cmd"].replace("pip install ", "").strip()
print(f"\nEXECUTION: requests not found — installing {pkg}...")
subprocess.check_call([sys.executable, "-m", "pip", "install", pkg])
import requests
TEST_HOST = "checklist.day"
TEST_URL = f"https://{TEST_HOST}"
print()
print(f"EXECUTION: checking SSL certificate for {TEST_HOST}...")
cert_data = get_cert_info(TEST_HOST)
cert = cert_data["cert"]
expiry = check_expiry(cert)
hostname_match = check_hostname_match(cert, TEST_HOST)
issuer = check_issuer(cert)
status_code = verify_https_request(TEST_URL)
print(f" protocol : {cert_data['protocol']}")
print(f" cipher : {cert_data['cipher'][0]}")
print(f" expires : {expiry['expiry_date']} ({expiry['days_remaining']} days remaining)")
print(f" hostname match: {hostname_match}")
print(f" issuer : {issuer['issuer_org']} ({issuer['issuer_cn']})")
print(f" self-signed : {issuer['is_self_signed']}")
print(f" https status : {status_code}")
# FM-2.4: surface warnings — do not withhold
if expiry["within_buffer"] and not expiry["expired"]:
print(f"\n [!] WARNING: cert expires in {expiry['days_remaining']} days — renew before deployment")
if expiry["expired"]:
print(f"\n [!] CRITICAL: cert is EXPIRED — do not deploy")
if issuer["is_self_signed"]:
print(f"\n [!] WARNING: self-signed certificate — not trusted by external clients")
# ----------------------------------------
# POST_EXECUTION
# FM-3.2: verify all checks before asserting PASS
# FM-3.3: exact assertions on each check
# ----------------------------------------
assert not expiry["expired"], \
f"FAIL: certificate is expired as of {expiry['expiry_date']}"
assert expiry["days_remaining"] > EXPIRY_BUFFER_DAYS, \
f"FAIL: cert expires in {expiry['days_remaining']} days — below {EXPIRY_BUFFER_DAYS}-day buffer"
assert hostname_match, \
f"FAIL: hostname '{TEST_HOST}' does not match cert CN/SAN"
assert not issuer["is_self_signed"], \
f"FAIL: self-signed certificate detected — not valid for production"
assert status_code == 200, \
f"FAIL: HTTPS request returned {status_code}, expected 200"
# Verify protocol is TLS 1.2 or higher
assert cert_data["protocol"] in ("TLSv1.2", "TLSv1.3"), \
f"FAIL: insecure protocol '{cert_data['protocol']}' — require TLS 1.2+"
print()
print("POST_EXECUTION: cert not expired ✓")
print(f"POST_EXECUTION: expiry buffer ok ✓ ({expiry['days_remaining']} days > {EXPIRY_BUFFER_DAYS}-day minimum)")
print("POST_EXECUTION: hostname match ✓")
print("POST_EXECUTION: trusted CA issuer ✓")
print("POST_EXECUTION: HTTPS request succeeded ✓")
print(f"POST_EXECUTION: protocol verified ✓ ({cert_data['protocol']})")
result = {
"status": "pass",
"cert_not_expired": True,
"expiry_buffer_ok": True,
"hostname_match": True,
"trusted_ca": True,
"https_request_ok": True,
"tls_protocol_verified": True,
"days_remaining": expiry["days_remaining"],
"protocol": cert_data["protocol"],
}
print(result)
print("PASS")