Python’s ssl module wraps sockets with TLS/SSL encryption and certificate verification. import ssl. create_default_context: ctx = ssl.create_default_context() — client context that verifies server certificates against system CAs; ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) — for servers. wrap_socket: ctx.wrap_socket(sock, server_hostname="example.com") → SSLSocket. SSLContext: ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) — sets check_hostname=True and verify_mode=CERT_REQUIRED; ssl.PROTOCOL_TLS_SERVER for servers; ctx.load_cert_chain("cert.pem", "key.pem") — load server cert. Custom CA: ctx.load_verify_locations("ca.pem"). Disable verification (testing only): ctx.check_hostname = False; ctx.verify_mode = ssl.CERT_NONE. Protocol options: ctx.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 — require TLS 1.2+. getpeercert: ssl_sock.getpeercert() → dict with subject, issuer, notAfter, subjectAltName. Cipher info: ssl_sock.cipher() → (name, protocol, bits); ssl_sock.version() → “TLSv1.3”. Cert loading: ssl.PEM_cert_to_DER_cert(pem), ssl.DER_cert_to_PEM_cert(der). get_server_certificate: ssl.get_server_certificate((host, port)) → PEM string. Session reuse: ctx.session_tickets = True. ALPN: ctx.set_alpn_protocols(["http/1.1"]). Claude Code generates mutual-TLS clients, certificate expiry checkers, self-signed test setups, and HTTPS proxy tools.
CLAUDE.md for ssl
## ssl Stack
- Stdlib: import ssl
- Client: ctx = ssl.create_default_context()
- with ctx.wrap_socket(sock, server_hostname=host) as ssock: ...
- Server: ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
- ctx.load_cert_chain("cert.pem", "key.pem")
- mTLS: ctx.verify_mode = ssl.CERT_REQUIRED; ctx.load_verify_locations("ca.pem")
- Check: ssl.get_server_certificate((host, 443))
ssl Secure Connection Pipeline
# app/sslutil.py — TLS client, cert inspect, expiry check, mTLS, test setup
from __future__ import annotations
import datetime
import socket
import ssl
import struct
import time
from contextlib import contextmanager
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Generator
# ─────────────────────────────────────────────────────────────────────────────
# 1. Client context helpers
# ─────────────────────────────────────────────────────────────────────────────
def default_client_ctx(
cafile: str | None = None,
min_tls: str = "TLSv1.2",
) -> ssl.SSLContext:
"""
Create a secure client SSL context verifying server certs against system CAs.
Pass cafile to verify against a custom CA bundle instead.
Example:
ctx = default_client_ctx()
with ctx.wrap_socket(sock, server_hostname="api.example.com") as ssock:
ssock.sendall(b"...")
"""
ctx = ssl.create_default_context(cafile=cafile)
ctx.minimum_version = {
"TLSv1.2": ssl.TLSVersion.TLSv1_2,
"TLSv1.3": ssl.TLSVersion.TLSv1_3,
}.get(min_tls, ssl.TLSVersion.TLSv1_2)
return ctx
def insecure_client_ctx() -> ssl.SSLContext:
"""
Create an SSL context that disables certificate verification.
FOR TESTING / INTERNAL NETWORKS ONLY — never use in production.
Example:
ctx = insecure_client_ctx()
with ctx.wrap_socket(sock, server_hostname="localhost") as ssock: ...
"""
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
return ctx
def mtls_client_ctx(
client_cert: str,
client_key: str,
cafile: str | None = None,
) -> ssl.SSLContext:
"""
Create a mutual-TLS client context that presents a certificate to the server.
Example:
ctx = mtls_client_ctx("client.pem", "client.key", cafile="ca.pem")
"""
ctx = default_client_ctx(cafile=cafile)
ctx.load_cert_chain(certfile=client_cert, keyfile=client_key)
return ctx
# ─────────────────────────────────────────────────────────────────────────────
# 2. Server context helpers
# ─────────────────────────────────────────────────────────────────────────────
def server_ctx(
certfile: str,
keyfile: str,
cafile: str | None = None,
require_client_cert: bool = False,
) -> ssl.SSLContext:
"""
Create an SSL server context from a certificate and key.
Pass cafile + require_client_cert=True for mutual TLS.
Example:
ctx = server_ctx("server.pem", "server.key")
ssl_sock = ctx.wrap_socket(plain_sock, server_side=True)
"""
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain(certfile=certfile, keyfile=keyfile)
ctx.minimum_version = ssl.TLSVersion.TLSv1_2
ctx.options |= ssl.OP_NO_COMPRESSION
if cafile or require_client_cert:
ctx.load_verify_locations(cafile)
ctx.verify_mode = ssl.CERT_REQUIRED if require_client_cert else ssl.CERT_OPTIONAL
return ctx
# ─────────────────────────────────────────────────────────────────────────────
# 3. Certificate inspection
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class CertInfo:
subject: dict[str, str]
issuer: dict[str, str]
san: list[str] # subjectAltName values
not_before: datetime.datetime
not_after: datetime.datetime
serial: str
version: int
@property
def days_until_expiry(self) -> int:
return (self.not_after.replace(tzinfo=None) - datetime.datetime.utcnow()).days
@property
def is_expired(self) -> bool:
return self.days_until_expiry < 0
def __str__(self) -> str:
subj = ", ".join(f"{k}={v}" for k, v in self.subject.items())
return (
f"Subject: {subj}\n"
f"SAN: {self.san}\n"
f"Valid: {self.not_before.date()} – {self.not_after.date()} "
f"({self.days_until_expiry} days remaining)"
)
def _parse_cert_dict(raw: dict) -> CertInfo:
"""Parse the dict from SSLSocket.getpeercert() into CertInfo."""
def dn_to_dict(tuples: tuple) -> dict[str, str]:
return {k: v for rdn in tuples for k, v in rdn}
def parse_time(s: str) -> datetime.datetime:
# format: "Jan 1 00:00:00 2025 GMT"
return datetime.datetime.strptime(s.strip(), "%b %d %H:%M:%S %Y %Z")
san = [v for _, v in raw.get("subjectAltName", [])]
return CertInfo(
subject=dn_to_dict(raw.get("subject", ())),
issuer=dn_to_dict(raw.get("issuer", ())),
san=san,
not_before=parse_time(raw["notBefore"]),
not_after=parse_time(raw["notAfter"]),
serial=str(raw.get("serialNumber", "")),
version=raw.get("version", 0),
)
def inspect_live_cert(host: str, port: int = 443, timeout: float = 5.0) -> CertInfo:
"""
Connect to host:port and return the server's certificate as CertInfo.
Example:
info = inspect_live_cert("python.org")
print(info)
"""
ctx = default_client_ctx()
with socket.create_connection((host, port), timeout=timeout) as sock:
with ctx.wrap_socket(sock, server_hostname=host) as ssock:
raw = ssock.getpeercert()
return _parse_cert_dict(raw)
def get_server_pem(host: str, port: int = 443, timeout: float = 5.0) -> str:
"""
Fetch the server's PEM certificate string without verification.
Example:
pem = get_server_pem("example.com")
Path("server.pem").write_text(pem)
"""
return ssl.get_server_certificate((host, port), timeout=timeout)
def load_pem_cert_info(pem: str) -> dict[str, Any]:
"""
Parse a PEM certificate (DER-decoded) and return raw openssl-style dict.
Requires tempfile loading through SSLContext — uses a one-shot approach.
Example:
pem = Path("cert.pem").read_text()
info = load_pem_cert_info(pem)
"""
import tempfile
with tempfile.NamedTemporaryFile(mode="w", suffix=".pem", delete=False) as tf:
tf.write(pem)
tf_path = tf.name
try:
ctx = ssl.SSLContext()
ctx.load_verify_locations(tf_path)
# Access via DER round-trip
der = ssl.PEM_cert_to_DER_cert(pem)
return {"der_length": len(der), "pem_length": len(pem)}
finally:
Path(tf_path).unlink(missing_ok=True)
# ─────────────────────────────────────────────────────────────────────────────
# 4. Expiry monitoring
# ─────────────────────────────────────────────────────────────────────────────
def check_cert_expiry(host: str, port: int = 443, timeout: float = 5.0) -> tuple[bool, int]:
"""
Return (expired, days_remaining) for the server certificate.
Example:
expired, days = check_cert_expiry("api.example.com")
if days < 30:
alert("cert expires soon!")
"""
try:
info = inspect_live_cert(host, port, timeout=timeout)
return info.is_expired, info.days_until_expiry
except Exception:
return True, -1 # treat connection failure as expired
def check_certs_bulk(endpoints: list[tuple[str, int]], warn_days: int = 30) -> list[dict]:
"""
Check certificate expiry for multiple endpoints; return status dicts.
Example:
report = check_certs_bulk([("api.example.com", 443), ("cdn.example.com", 443)])
for r in report:
print(f"{r['host']}:{r['port']} — {r['days']} days")
"""
from concurrent.futures import ThreadPoolExecutor, as_completed
def _check(h: str, p: int) -> dict:
expired, days = check_cert_expiry(h, p)
return {
"host": h, "port": p, "days": days,
"expired": expired, "warning": days < warn_days and not expired,
}
with ThreadPoolExecutor(max_workers=len(endpoints)) as ex:
futures = {ex.submit(_check, h, p): (h, p) for h, p in endpoints}
return [fut.result() for fut in as_completed(futures)]
# ─────────────────────────────────────────────────────────────────────────────
# 5. TLS connection details
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class TLSHandshakeInfo:
host: str
port: int
protocol: str # e.g. "TLSv1.3"
cipher: str # cipher suite name
bits: int # key bits
alpn: str | None # negotiated ALPN protocol
def __str__(self) -> str:
alpn_str = f" ALPN={self.alpn}" if self.alpn else ""
return f"{self.host}:{self.port} {self.protocol} {self.cipher} ({self.bits}-bit){alpn_str}"
def tls_handshake_info(host: str, port: int = 443, timeout: float = 5.0) -> TLSHandshakeInfo:
"""
Connect and return TLS handshake details without transferring application data.
Example:
info = tls_handshake_info("python.org")
print(info)
"""
ctx = default_client_ctx()
ctx.set_alpn_protocols(["http/1.1", "h2"])
with socket.create_connection((host, port), timeout=timeout) as sock:
with ctx.wrap_socket(sock, server_hostname=host) as ssock:
cipher_name, protocol, bits = ssock.cipher()
alpn = ssock.selected_alpn_protocol()
version = ssock.version() or protocol
return TLSHandshakeInfo(
host=host, port=port, protocol=version,
cipher=cipher_name, bits=bits or 0, alpn=alpn,
)
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print("=== ssl demo ===")
# All demos use public internet hosts — skip gracefully if offline
test_hosts = [("python.org", 443)]
for host, port in test_hosts:
print(f"\n--- {host}:{port} ---")
try:
info = tls_handshake_info(host, port, timeout=5.0)
print(f" TLS info: {info}")
cert = inspect_live_cert(host, port, timeout=5.0)
print(f" Cert CN: {cert.subject.get('commonName', cert.subject)}")
print(f" SAN: {cert.san[:3]}")
print(f" Expires: {cert.not_after.date()} ({cert.days_until_expiry} days)")
print(f" Expired: {cert.is_expired}")
except OSError as e:
print(f" Network error: {e} (offline?)")
print("\n--- default_client_ctx ---")
ctx = default_client_ctx()
print(f" min_version: {ctx.minimum_version}")
print(f" verify_mode: {ctx.verify_mode}")
print(f" check_hostname: {ctx.check_hostname}")
print("\n--- insecure_client_ctx (testing only) ---")
ctx_insecure = insecure_client_ctx()
print(f" verify_mode: {ctx_insecure.verify_mode}")
print(f" check_hostname: {ctx_insecure.check_hostname}")
print("\n=== done ===")
For the requests / httpx alternative — requests and httpx (both PyPI) handle TLS automatically through their Session objects; requests uses certifi for its CA bundle; they also manage HTTP parsing, cookies, redirects, and connection pooling — use requests or httpx for all HTTPS API communication; use ssl directly when you need custom TLS configuration (mutual TLS, custom CA pinning, non-HTTP protocols like SMTP over TLS, FTPS, or raw TLS socket servers). For the cryptography / pyOpenSSL alternative — cryptography (PyPI) provides full access to X.509 certificate creation, parsing, signing, PKCS, and PEM/DER conversion without the overhead of a C OpenSSL binding; pyOpenSSL wraps libssl directly; both offer more certificate introspection than stdlib ssl.SSLSocket.getpeercert() — use cryptography when you need to parse certificate extensions, generate self-signed certificates programmatically, or implement a PKI tool; use ssl for application-layer TLS configuration where you just need to secure an existing socket connection. The Claude Skills 360 bundle includes ssl skill sets covering default_client_ctx()/insecure_client_ctx()/mtls_client_ctx() client context helpers, server_ctx() server context builder, CertInfo dataclass with inspect_live_cert()/get_server_pem(), check_cert_expiry()/check_certs_bulk() expiry monitoring, and TLSHandshakeInfo with tls_handshake_info(). Start with the free tier to try TLS security patterns and ssl pipeline code generation.